Search Unity

How do I use ColliderCast properly?

Discussion in 'Physics for ECS' started by colin_young, Nov 7, 2021.

  1. colin_young

    colin_young

    Joined:
    Jun 1, 2017
    Posts:
    243
    I am trying to convert a raycast vehicle system to use cylinder colliders for the wheels. I cannot figure out how to get the collider from the GO hierarchy and then use it in the physics system. I have my vehicle wheels set up as in the Unity ECS Raycast Vehicle Demo (Vehicle -> Suspension -> Wheel -> Wheel Graphic) and have added a Physics Shape to the wheel graphic:

    Screenshot - editor.jpg

    In my GameObjectConversionSystem script I create a Cylinder Collider:

    Code (CSharp):
    1. var wheelAuthoring = wheelRepresentation.GetComponent<PhysicsShapeAuthoring>();
    2. var wheelGeometry = wheelAuthoring?.GetCylinderProperties();
    3.  
    4. if (wheelGeometry.HasValue) {
    5.     var filter = new CollisionFilter
    6.     {
    7.         BelongsTo = ~0u,
    8.         CollidesWith = 0u,
    9.         GroupIndex = 0
    10.     };
    11.     var wheelCollider = CylinderCollider.Create(wheelGeometry.Value);
    12.     DstEntityManager.AddComponentData(wheelEntity, new PhysicsCollider { Value = wheelCollider });
    13. }
    14.  
    In the vehicle physics system, I attempt to use ColliderCast:

    Code (CSharp):
    1. var orientation = quaternion.identity;
    2. var input = new ColliderCastInput()
    3. {
    4.     Collider = collider.ColliderPtr,
    5.     Orientation = orientation,
    6.     Start = rayStart,
    7.     End = rayEnd
    8. };
    9.  
    10. var colliderHit = world.CastCollider(input, out var rayResult);
    11. var hit = rayResult.Entity == Entity.Null;
    12.  
    13. var hitBody = world.Bodies[rayResult.RigidBodyIndex];
    14. var hitEntity = hitBody.Entity;
    15. var hitColliderKey = rayResult.ColliderKey.Value;
    16.  
    17. if (mechanics.drawDebugInformation != 0)
    18. {
    19.     Debug.Log($"Entity: {entity} - Hit: {hitEntity} @ {rayResult.Fraction}, {rayResult.Position}: {hitColliderKey}");
    20.     Debug.DrawRay(rayStart, rayResult.Position - rayStart, Color.magenta, 0.01f);
    21. }
    22.  
    But the output of the debug always looks like this:

    Entity(70:1) - Hit: Entity(70:1) @ 0, float3(194.284f, -0.04390742f, 171.9057f): 4294967295 


    Either world.Bodies[rayResult.RigidBodyIndex] is returning the current entity, or the collider I am trying to cast is colliding with itself. If I try to set up a filter on the collider when I am converting, things blow up almost immediately (my vehicle is launched into space and the colliders claim to be colliding with things well outside of the parameters of the cast length), but at least that confirms my suspicion that it seems to be colliding with itself, along with the fraction = 0 of the result.

    I am certain I am missing something fundamental here. This seems like a fairly standard use-case (i.e. one wants to create a hierarchy in the editor, assign physics shapes and then use those shapes in collision tests), but I can't find a simple, straightforward example that does that. Can anyone point out what I've missed, or what I need to do to get this fairly simple scenario working?
     
    Last edited: Nov 7, 2021
    mcroswell and Botaurus like this.
  2. thelebaron

    thelebaron

    Joined:
    Jun 2, 2013
    Posts:
    857
  3. CookieStealer2

    CookieStealer2

    Joined:
    Jun 25, 2018
    Posts:
    119
    Here's an example of a collector that can ignore entities:
    Code (CSharp):
    1.  
    2. public struct AnyHitWithIgnoreCollector<T> : ICollector<T> where T : struct, IQueryResult
    3. {
    4.     public bool EarlyOutOnFirstHit => true;
    5.     public float MaxFraction { get; }
    6.     public int NumHits => 0;
    7.     public FixedList32<Entity> entitiesToIgnore;
    8.     public AnyHitWithIgnoreCollector(float maxFraction, FixedList32<Entity> entitiesToIgnore) : this()
    9.     {
    10.         this.MaxFraction = maxFraction;
    11.         this.entitiesToIgnore = entitiesToIgnore;
    12.     }
    13.     public AnyHitWithIgnoreCollector(float maxFraction, Entity entityToIgnore) : this()
    14.     {
    15.         MaxFraction = maxFraction;
    16.         entitiesToIgnore = new FixedList32<Entity>();
    17.         entitiesToIgnore.Add(entityToIgnore);
    18.     }
    19.     public AnyHitWithIgnoreCollector(float maxFraction, Entity entityToIgnore1, Entity entityToIgnore2) : this()
    20.     {
    21.         MaxFraction = maxFraction;
    22.         entitiesToIgnore = new FixedList32<Entity>();
    23.         entitiesToIgnore.Add(entityToIgnore1);
    24.         entitiesToIgnore.Add(entityToIgnore2);
    25.     }
    26.     public bool AddHit(T hit)
    27.     {
    28.         if (entitiesToIgnore.Contains(hit.Entity)) return false;
    29.         Assert.IsTrue(hit.Fraction < MaxFraction);
    30.         return true;
    31.     }
    32. }
    33.  
     
    mcroswell likes this.
  4. colin_young

    colin_young

    Joined:
    Jun 1, 2017
    Posts:
    243
    Thanks! Late last night I discovered I was using PhysicsWorld, not ColliderWorld to run the CastCollider. Fixing that and adding GroupIndex = -1 solved the self-collision problem.

    But is that really how we are intended to do it? Is self-colliding really the default? These questions are more directed to Unity I suppose, since only they know what the intended use is. That just doesn't seem sensible to me. In my case, when I'm converting prefab instances to entities, if I want wheels of different vehicles to collide with each other, I need to assign a unique GroupId to each vehicle? And what if I want the wheels on a vehicle to be able to collide with each other? I need to assign an id that is unique within a single vehicle and across all vehicles? I can't help but feel like I've done something wrong or am missing something.

    In any case, I am now colliding with the ground correctly. I just need to figure out why my normal forces are all zero now, and, as a result, there is no traction to drive the vehicle.
     
    mcroswell, Botaurus and Clonkex like this.
  5. Botaurus

    Botaurus

    Joined:
    Feb 20, 2013
    Posts:
    81
    I ran into this problem too and it was pretty confusing.
    in Unity's Collector.cs I found this collector:

    Code (CSharp):
    1. // A collector used to provide filtering for QueryInteraction enum and a specified entity (usually used for self-hit filtering).
    2.     // This is a wrapper of the user provided collector, which serves to enable
    3.     // filtering based on the QueryInteraction parameter.
    4.     internal unsafe struct QueryInteractionCollector<T, C> : ICollector<T>
    5.         where T : struct, IQueryResult
    6.         where C : struct, ICollector<T>
    Would be great for this to be included in the docs since it's a problem most people are likely to encounter, and the solution exists but is somewhat hidden.
     
    mcroswell and JMPM-UNITY like this.
  6. JMPM-UNITY

    JMPM-UNITY

    Unity Technologies

    Joined:
    Jun 16, 2021
    Posts:
    94
    Hey @Botaurus, thanks for your feedback I will open a new user request regarding the matter and likely include this on the physics documentation page.
     
  7. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
    How are we suposed to use this ?

    The
    QueryInteractionCollector<T, C>
    is internal and the only place where it make sense to use it is in the
    ColliderAspect
    but this one does not even use the Entity from the aspect to filter the result and instead puts an
    Entity.Null
    (see line 16).



    Code (CSharp):
    1.         /// <summary>   Cast another collider aspect against this one. </summary>
    2.         ///
    3.         /// <typeparam name="T">    Generic type parameter. </typeparam>
    4.         /// <param name="colliderAspect">   The collider aspect. </param>
    5.         /// <param name="direction">        The direction of the input aspect. </param>
    6.         /// <param name="maxDistance">      The maximum distance. </param>
    7.         /// <param name="collector">        [in,out] The collector. </param>
    8.         /// <param name="queryInteraction"> (Optional) The query interaction. </param>
    9.         ///
    10.         /// <returns>   True if there is a hit, false otherwise. </returns>
    11.         public bool CastCollider<T>(in ColliderAspect colliderAspect, float3 direction, float maxDistance, ref T collector, QueryInteraction queryInteraction = QueryInteraction.Default) where T : struct, ICollector<ColliderCastHit>
    12.         {
    13.             ColliderCastInput input = new ColliderCastInput(colliderAspect.m_Collider.ValueRO.Value, colliderAspect.Position, colliderAspect.Position + direction * maxDistance, colliderAspect.Rotation, colliderAspect.Scale);
    14.             if (queryInteraction == QueryInteraction.IgnoreTriggers)
    15.             {
    16.                 QueryInteractionCollector<ColliderCastHit, T> interactionCollector = new QueryInteractionCollector<ColliderCastHit, T>(ref collector, true, Entity.Null);
    17.                 return CastCollider(input, ref interactionCollector);
    18.             }
    19.             else
    20.             {
    21.                 return CastCollider(input, ref collector);
    22.             }
    23.         }