Having a rough time with CastRay Collectors

My requirements are:

  • ignore the first hit or backface (ray always originates inside a body)
  • return a hit from dynamic or static colliders, a hit is a hit

The purpose is to implement avoidance for my agents. I want them to cast a ray toward their target and if another agent is detected (or suitable obstacle) then avoid it. Easier said than done with collectors. The API is confusing and probably a copy of Havok’s at a guess.

I found this collector which I tried, however it’s still confusing and doesn’t detect like I’d expect. It takes bodies - and not all objects the ray hits have a body! So I think it’s a good start but I’m not sure how to sort of basically just emulate Physx default behaviour in Unity as it’s terribly useful for general game dev.

I think, looking at this code, it rejects primitive shapes, so that might be it. But not sure how to modify to the above requirements. I am generally hacking around at this point and it’s not a good feeling.

    struct FrontFaceClosestHitCollector : ICollector<RaycastHit>
    {
        // ICollector
        public bool EarlyOutOnFirstHit => false;
        public float MaxFraction { get; private set; }
        public int NumHits { get; private set; }

        // Input
        public NativeSlice<RigidBody> Bodies;

        // Output
        public RaycastHit ClosestHit { get; private set; }

        public FrontFaceClosestHitCollector(NativeSlice<RigidBody> bodies)
        {
            MaxFraction = 1f;
            NumHits = 0;
            ClosestHit = default;
            Bodies = bodies;
        }

        public bool AddHit(RaycastHit hit)
        {
            if (!IsFrontFaceHit(hit)) return false;

            MaxFraction = hit.Fraction;
            ClosestHit = hit;
            NumHits = 1;
            return true;
        }

        unsafe bool IsFrontFaceHit(RaycastHit hit)
        {
            var body = Bodies[hit.RigidBodyIndex];

            if (!body.Collider.Value.GetLeaf(hit.ColliderKey, out var childCollider)) return false;

            var childColliderType = childCollider.Collider->Type;
            var childColliderIsPolygon = childColliderType == ColliderType.Triangle || childColliderType == ColliderType.Quad;

            if (!childColliderIsPolygon) return false;

            var collider = (PolygonCollider*)childCollider.Collider;
            var localNormal = collider->Planes[0].Normal;
            var worldNormal = math.rotate(body.WorldFromBody, localNormal);

            return math.all(math.abs(hit.SurfaceNormal - worldNormal) <= math.EPSILON);
        }
    }

DOTS has two approaches of collision detection. Collector, which if I remember correctly, offers more options for collision detection.

And other is trigger collision. Which is like on enter, on exit etc.

So question is, which one you need.

I remember in past having a bit fight with collectors. I think I eventually made it work. But was while ago. 0.50 could also change many things since then.

Just regular raycast queries that ignore the volume the ray begins inside of.

Ah here we go:

    struct IgnoreEntityCollector : ICollector<RaycastHit>
    {
        // ICollector
        public bool EarlyOutOnFirstHit => false;
        public float MaxFraction { get; private set; }
        public int NumHits { get; private set; }

        // Input
        public Entity IgnoreEntity;

        // Output
        public RaycastHit ClosestHit { get; private set; }

        public IgnoreEntityCollector(Entity ignoreEntity)
        {
            MaxFraction = 1f;
            NumHits = 0;
            ClosestHit = default;
            IgnoreEntity = ignoreEntity;
        }

        public bool AddHit(RaycastHit hit)
        {
            if (hit.Entity == IgnoreEntity) return false;
            MaxFraction = hit.Fraction;
            ClosestHit = hit;
            NumHits = 1;
            return true;
        }
    }

It’s all a lot simpler than I thought. I was just confused by the problem space and the havok thinking. Collectors are very simple to understand and controllable. I wasn’t confident about what I was allowed to try or not. Simply trying the above gave me solid results. I just pass in the entity ignore and done.

Please let me know if it’s right or wrong to do this, and if it’s higher performance than doing a cast-all (I imagine it surely must be).

I realised my raycast comparison approach (not posted here) was not working because backfaces also give a correct raycast given a ray hitting it. So I couldn’t filter it out by a dot product. This way seems simpler though, ignoring an entity or even id.

I will say that I do see a lot of smart people on this part of the forum doing solutions that are a bit overkill or more complex than the problem they’re trying to solve. I believe this must be due to a combination of older API and learning that old API back then. Surely a lot changed between 0.1x and 0.50x : )

1 Like

On the topic of collectors. (I started with 2 but I found more)
Things that really suck about them:

  • Hardcoded early out with EarlyOutOnFirstHit: Okay, we don’t need an early out on every collector. Hardly any collector has an early out so why is there a hardcoded if in the loop?
  • No hitCount information in AddHit: AddHit is called in a for loop i < hitCount, yet we don’t know how many hits there are in AddHit.
  • NearestCollector: This runs through the whole loop for every hit and overwrites all data in AddHit because the last one is the nearest. This is hacky AF!
  • When hits are actually sorted, why doesn’t it start from the nearest or is controllable which direction the loop runs.
  • NativeList instead of NativeArray: the loop knows how many element will end up in the list. Why not pre-allocate with the number of hits?
  • Implementing a performant capped NearestHitCollector is a pain because of all this. (typical RPG skills, hit n nearest targets)

edit: On further reflection, the mentioned for loop could be called many times with makes my points about the hitCount and pre-allocation void. I have to study the code a bit more to figure out if that’s the case. Most likely it is.

1 Like