Search Unity

Question Distance Query not working

Discussion in 'Physics for ECS' started by aveakrause, Jun 13, 2020.

  1. aveakrause

    aveakrause

    Joined:
    Oct 3, 2018
    Posts:
    70
    Anyone able to tell me why CalculateDistance always fails no matter what? The input seems correct to me.

    Code (CSharp):
    1.  
    2.          
    3.     protected override void OnUpdate()
    4.     {
    5.         EntityCommandBuffer.Concurrent ecb = entityCommandBufferSystem.CreateCommandBuffer().ToConcurrent();
    6.         ComponentDataFromEntity<PhysicsCollider> colliderData = GetComponentDataFromEntity<PhysicsCollider>(true);
    7.         ComponentDataFromEntity<LocalToWorld> localToWorldData = GetComponentDataFromEntity<LocalToWorld>(true);
    8.  
    9.         Entities.WithAll<Tag_TargetAcquired>().ForEach(
    10.             (Entity entity, int entityInQueryIndex, ref TargetComponent targetData, in AwarenessDistance awarenessDistanceData) =>
    11.             {
    12.                 // Make sure we really have a target
    13.                 if(targetData.target == Entity.Null)
    14.                     return;
    15.  
    16.                 // Our target is dead, forget about it
    17.                 if(HasComponent<Tag_Dead>(targetData.target))
    18.                 {
    19.                     targetData = default;
    20.                     ecb.RemoveComponent<Tag_TargetAcquired>(entityInQueryIndex, entity);
    21.                     return;
    22.                 }
    23.  
    24.                 // If both entities have colliders, calculate the distance between the two with respects to the collider
    25.                 // This API may change, because this backwards and silly. Update this when the API changes (this will likely break)
    26.                 if(HasComponent<PhysicsCollider>(entity) && HasComponent<PhysicsCollider>(targetData.target))
    27.                     targetData.distanceToTarget = colliderData[entity].Value.Value.CalculateDistance(
    28.                         new ColliderDistanceInput{
    29.                             Collider = colliderData[targetData.target].ColliderPtr,
    30.                             Transform = new RigidTransform(math.mul(
    31.                                 math.inverse(localToWorldData[entity].Value),
    32.                                 localToWorldData[targetData.target].Value
    33.                             )), MaxDistance = awarenessDistanceData.Value
    34.                         }, out DistanceHit distanceHit
    35.                     ) ? distanceHit.Distance : float.PositiveInfinity;
    36.  
    37.                 // Forget the target when out of range
    38.                 if(targetData.distanceToTarget > awarenessDistanceData.Value)
    39.                 {
    40.                     targetData = default;
    41.                     ecb.RemoveComponent<Tag_TargetAcquired>(entityInQueryIndex, entity);
    42.                 }
    43.             }
    44.         ).WithReadOnly(colliderData).WithReadOnly(localToWorldData).ScheduleParallel();
    45.  
    46.         entityCommandBufferSystem.AddJobHandleForProducer(this.Dependency);
    47.     }
    48.  
    (yes, it's a beautiful ternary lol)

    "Transform: The Input collider's transform in the calling Collider's local space." is the least clear thing I have ever heard. Why can't Unity put these weird inverse matrix multiplications behind the scenes?
     
    Last edited: Jun 13, 2020
  2. petarmHavok

    petarmHavok

    Joined:
    Nov 20, 2018
    Posts:
    461
    If you query on world or body, you don't have this issue of converting to collider local space, since it's converted internally for you. But querying on the collider really makes sense to be done in collider space, as it's sort of the owner of the "world" seen by that query.

    Now, for your particular example, it seems like you are correctly converting your input to collider space. And since you only need the distance, you don't need to convert back to world space (you don't need the position of the hit). What are you observing? Query always returning false, or the distance is too high?
     
    steveeHavok likes this.
  3. steveeHavok

    steveeHavok

    Joined:
    Mar 19, 2019
    Posts:
    481
    Also, the first thing the query does it check the CollisionFilter of both collider to ensure the collision is enabled.
    So make sure the CollisionFilter on each body is setup so that they will collide.
     
  4. aveakrause

    aveakrause

    Joined:
    Oct 3, 2018
    Posts:
    70
    It always returns false.

    The 2 units can't collide. There are potentially 30k-60k of either unit type, enabling collision between them would bring my fps to 0. Them not being able to collide in the physics simulation shouldn't effect my ability to calculate a distance between them when needed, however... Is there a way for me to pass a specific collision filter for that calculate call rather than to set it on the bodies as a whole?
     
  5. petarmHavok

    petarmHavok

    Joined:
    Nov 20, 2018
    Posts:
    461
    You can temporarily change the collision filter on both colliders to default, do the query, and then immediately revert to old values.
     
  6. aveakrause

    aveakrause

    Joined:
    Oct 3, 2018
    Posts:
    70
    I'll look at trying that out and get back to you, thanks for the tip!

    Would we be able to return an enum rather than a bool with more information? Such as Success, CanNotCollide, ColliderNotFound, etc? We see a good example of this with Unity's experimental nav mesh stuff.

    Also might be worth adding to the docs that the calculate distance check requires the colliders to be able to collide, as that's not immediately apparent nor easy to figure out. I even did some decompiling in ILSpy and wasn't able to see that as it got stuck at the ICollider implementation during step through.
     
  7. petarmHavok

    petarmHavok

    Joined:
    Nov 20, 2018
    Posts:
    461
    Usually, what you can do to track down issues like these is to make your physics package local (move it from PackageCache to Packages and add it as a local package), that should help a lot in debugging.

    It's an interesting suggestion to add this enum. While I see great improvement in usability, I'm slightly worried about performance. Query code is highly optimized and adding branching (if something, else if something else...) can affect performance badly. But we'll certainly give it a go.

    Another idea is to simply disregard filters on direct collider vs. collider queries, and use them only on world level. How does that sound?
     
  8. aveakrause

    aveakrause

    Joined:
    Oct 3, 2018
    Posts:
    70
    That's how I assumed it worked in the first place. I'm surprised it didn't. Thanks a ton for helping me solve this though. Too many far confused hours went into it. Maybe even accept a collision filter as an optional param. I can see how that may be useful.
     
    Last edited: Jun 17, 2020
    petarmHavok likes this.
  9. aveakrause

    aveakrause

    Joined:
    Oct 3, 2018
    Posts:
    70
    Looks like I got it working!

    Code (CSharp):
    1.  
    2.                 if(HasComponent<PhysicsCollider>(entity) && HasComponent<PhysicsCollider>(targetData.target))
    3.                 {
    4.                     CollisionFilter entityOrgFilter = colliderData[entity].ColliderPtr->Filter;
    5.                     CollisionFilter targetOrgFilter = colliderData[targetData.target].ColliderPtr->Filter;
    6.  
    7.                     colliderData[entity].ColliderPtr->Filter = CollisionFilter.Default;
    8.                     colliderData[targetData.target].ColliderPtr->Filter = CollisionFilter.Default;
    9.  
    10.                     targetData.distanceToTarget = colliderData[entity].Value.Value.CalculateDistance(
    11.                         new ColliderDistanceInput{
    12.                             Collider = colliderData[targetData.target].ColliderPtr,
    13.                             Transform = new RigidTransform(math.mul(
    14.                                 math.inverse(localToWorldData[entity].Value),
    15.                                 localToWorldData[targetData.target].Value
    16.                             )), MaxDistance = awarenessDistanceData.Value
    17.                         }, out DistanceHit distanceHit
    18.                     ) ? distanceHit.Distance : awarenessDistanceData.Value + 1;
    19.                    
    20.                     colliderData[entity].ColliderPtr->Filter = entityOrgFilter;
    21.                     colliderData[targetData.target].ColliderPtr->Filter = targetOrgFilter;
    22.                 }
     
    Last edited: Jun 18, 2020
    petarmHavok and steveeHavok like this.
  10. aveakrause

    aveakrause

    Joined:
    Oct 3, 2018
    Posts:
    70
    On further inspection, this doesn't work. To get it working, I have to make it singlethreaded as colliderptr->Filter sets it for the whole archtype rather than the single body, resulting in the parallel reading the Default data from the filter then setting the filter back to Default rather than the original filter.

    With some optimization...
    Code (CSharp):
    1.  
    2.     protected override void OnUpdate()
    3.     {
    4.         EntityCommandBuffer ecb = entityCommandBufferSystem.CreateCommandBuffer();
    5.         ComponentDataFromEntity<PhysicsCollider> colliderData = GetComponentDataFromEntity<PhysicsCollider>(true);
    6.         ComponentDataFromEntity<LocalToWorld> localToWorldData = GetComponentDataFromEntity<LocalToWorld>(true);
    7.        
    8.         Collider* lastCollider = null;
    9.         CollisionFilter lastFilter = default;
    10.  
    11.         Entities.WithAll<Tag_TargetAcquired>().ForEach(
    12.             (Entity entity, ref TargetComponent targetData, in AwarenessDistance awarenessDistanceData) =>
    13.             {
    14.                 // Make sure we really have a target
    15.                 if(targetData.target == Entity.Null)
    16.                     return;
    17.  
    18.                 // Our target is dead, forget about it
    19.                 if(HasComponent<Tag_Dead>(targetData.target))
    20.                 {
    21.                     targetData = default;
    22.                     ecb.RemoveComponent<Tag_TargetAcquired>(entity);
    23.                     return;
    24.                 }
    25.  
    26.                 /* If both entities have colliders, calculate the distance between the two with respects to the collider.
    27.                 If Calculate Distance fails for whatever reason, we assume the target is just out of range
    28.                 Collider to Collider calculate distance first checks if the 2 colliders can collide. We can't do that to all of the bodies without 0 fps.
    29.                 So we cache the collision filter, set it to Default (belongs to and collides with everything), do the query, then reset to the proper collision filters */
    30.                 if(colliderData.HasComponent(entity) && colliderData.HasComponent(targetData.target))
    31.                 {
    32.                     if(lastCollider != colliderData[entity].ColliderPtr)
    33.                     {
    34.                         if(lastCollider != null)
    35.                             lastCollider->Filter = lastFilter;
    36.                        
    37.                         lastCollider = colliderData[entity].ColliderPtr;
    38.                         lastFilter = lastCollider->Filter;
    39.                         lastCollider->Filter = CollisionFilter.Default;
    40.                     }
    41.  
    42.                     targetData.distanceToTarget = colliderData[entity].Value.Value.CalculateDistance(
    43.                         new ColliderDistanceInput{
    44.                             Collider = colliderData[targetData.target].ColliderPtr,
    45.                             Transform = new RigidTransform(math.mul(
    46.                                 math.inverse(localToWorldData[entity].Value),
    47.                                 localToWorldData[targetData.target].Value
    48.                             )), MaxDistance = awarenessDistanceData.Value
    49.                         }, out DistanceHit distanceHit
    50.                     ) ? distanceHit.Distance : awarenessDistanceData.Value + 1;
    51.                    
    52.                     if(lastCollider != null)
    53.                         lastCollider->Filter = lastFilter;
    54.                 }
    55.  
    56.                 // Buildings forget their target when out of range
    57.                 // If the target is out of range, forget it
    58.                 if(targetData.distanceToTarget > awarenessDistanceData.Value)
    59.                 {
    60.                     targetData = default;
    61.                     ecb.RemoveComponent<Tag_TargetAcquired>(entity);
    62.                 }
    63.             }
    64.         ).WithReadOnly(colliderData).WithReadOnly(localToWorldData).Run();
    65.        
    66.         if(lastCollider != null)
    67.             lastCollider->Filter = lastFilter;
    68.     }
    69.  
     
    Last edited: Jun 19, 2020
    Baggers_ likes this.