Distance Query not working

Anyone able to tell me why CalculateDistance always fails no matter what? The input seems correct to me.

    protected override void OnUpdate()
    {
        EntityCommandBuffer.Concurrent ecb = entityCommandBufferSystem.CreateCommandBuffer().ToConcurrent();
        ComponentDataFromEntity<PhysicsCollider> colliderData = GetComponentDataFromEntity<PhysicsCollider>(true);
        ComponentDataFromEntity<LocalToWorld> localToWorldData = GetComponentDataFromEntity<LocalToWorld>(true);

        Entities.WithAll<Tag_TargetAcquired>().ForEach(
            (Entity entity, int entityInQueryIndex, ref TargetComponent targetData, in AwarenessDistance awarenessDistanceData) =>
            {
                // Make sure we really have a target
                if(targetData.target == Entity.Null)
                    return;

                // Our target is dead, forget about it
                if(HasComponent<Tag_Dead>(targetData.target))
                {
                    targetData = default;
                    ecb.RemoveComponent<Tag_TargetAcquired>(entityInQueryIndex, entity);
                    return;
                }

                // If both entities have colliders, calculate the distance between the two with respects to the collider
                // This API may change, because this backwards and silly. Update this when the API changes (this will likely break)
                if(HasComponent<PhysicsCollider>(entity) && HasComponent<PhysicsCollider>(targetData.target))
                    targetData.distanceToTarget = colliderData[entity].Value.Value.CalculateDistance(
                        new ColliderDistanceInput{
                            Collider = colliderData[targetData.target].ColliderPtr,
                            Transform = new RigidTransform(math.mul(
                                math.inverse(localToWorldData[entity].Value),
                                localToWorldData[targetData.target].Value
                            )), MaxDistance = awarenessDistanceData.Value
                        }, out DistanceHit distanceHit
                    ) ? distanceHit.Distance : float.PositiveInfinity;

                // Forget the target when out of range
                if(targetData.distanceToTarget > awarenessDistanceData.Value)
                {
                    targetData = default;
                    ecb.RemoveComponent<Tag_TargetAcquired>(entityInQueryIndex, entity);
                }
            }
        ).WithReadOnly(colliderData).WithReadOnly(localToWorldData).ScheduleParallel();

        entityCommandBufferSystem.AddJobHandleForProducer(this.Dependency);
    }

(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?

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?

1 Like

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.

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?

You can temporarily change the collision filter on both colliders to default, do the query, and then immediately revert to old values.

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.

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?

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.

1 Like

Looks like I got it working!

                if(HasComponent<PhysicsCollider>(entity) && HasComponent<PhysicsCollider>(targetData.target))
                {
                    CollisionFilter entityOrgFilter = colliderData[entity].ColliderPtr->Filter;
                    CollisionFilter targetOrgFilter = colliderData[targetData.target].ColliderPtr->Filter;

                    colliderData[entity].ColliderPtr->Filter = CollisionFilter.Default;
                    colliderData[targetData.target].ColliderPtr->Filter = CollisionFilter.Default;

                    targetData.distanceToTarget = colliderData[entity].Value.Value.CalculateDistance(
                        new ColliderDistanceInput{
                            Collider = colliderData[targetData.target].ColliderPtr,
                            Transform = new RigidTransform(math.mul(
                                math.inverse(localToWorldData[entity].Value),
                                localToWorldData[targetData.target].Value
                            )), MaxDistance = awarenessDistanceData.Value
                        }, out DistanceHit distanceHit
                    ) ? distanceHit.Distance : awarenessDistanceData.Value + 1;

                    colliderData[entity].ColliderPtr->Filter = entityOrgFilter;
                    colliderData[targetData.target].ColliderPtr->Filter = targetOrgFilter;
                }
2 Likes

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...

    protected override void OnUpdate()
    {
        EntityCommandBuffer ecb = entityCommandBufferSystem.CreateCommandBuffer();
        ComponentDataFromEntity<PhysicsCollider> colliderData = GetComponentDataFromEntity<PhysicsCollider>(true);
        ComponentDataFromEntity<LocalToWorld> localToWorldData = GetComponentDataFromEntity<LocalToWorld>(true);

        Collider* lastCollider = null;
        CollisionFilter lastFilter = default;

        Entities.WithAll<Tag_TargetAcquired>().ForEach(
            (Entity entity, ref TargetComponent targetData, in AwarenessDistance awarenessDistanceData) => 
            {
                // Make sure we really have a target
                if(targetData.target == Entity.Null)
                    return;

                // Our target is dead, forget about it
                if(HasComponent<Tag_Dead>(targetData.target))
                {
                    targetData = default;
                    ecb.RemoveComponent<Tag_TargetAcquired>(entity);
                    return;
                }

                /* If both entities have colliders, calculate the distance between the two with respects to the collider.
                If Calculate Distance fails for whatever reason, we assume the target is just out of range
                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.
                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 */
                if(colliderData.HasComponent(entity) && colliderData.HasComponent(targetData.target))
                {
                    if(lastCollider != colliderData[entity].ColliderPtr)
                    {
                        if(lastCollider != null)
                            lastCollider->Filter = lastFilter;

                        lastCollider = colliderData[entity].ColliderPtr;
                        lastFilter = lastCollider->Filter;
                        lastCollider->Filter = CollisionFilter.Default;
                    }

                    targetData.distanceToTarget = colliderData[entity].Value.Value.CalculateDistance(
                        new ColliderDistanceInput{
                            Collider = colliderData[targetData.target].ColliderPtr,
                            Transform = new RigidTransform(math.mul(
                                math.inverse(localToWorldData[entity].Value),
                                localToWorldData[targetData.target].Value
                            )), MaxDistance = awarenessDistanceData.Value
                        }, out DistanceHit distanceHit
                    ) ? distanceHit.Distance : awarenessDistanceData.Value + 1;

                    if(lastCollider != null)
                        lastCollider->Filter = lastFilter;
                }

                // Buildings forget their target when out of range
                // If the target is out of range, forget it
                if(targetData.distanceToTarget > awarenessDistanceData.Value)
                {
                    targetData = default;
                    ecb.RemoveComponent<Tag_TargetAcquired>(entity);
                }
            }
        ).WithReadOnly(colliderData).WithReadOnly(localToWorldData).Run();

        if(lastCollider != null)
            lastCollider->Filter = lastFilter;
    }
1 Like