Easy Way to View Collider Casts

Hello, I am pretty new to data-oriented design and have been attempting to implement the functionality of flocking like the boids demo, using just the data-oriented tech stack. I have the 3 main pillars of boids: separation, cohesion, and alignment, working fairly well, but now I am trying to implement object avoidance and having a bit of trouble.

Right now I hve a system that casts a sphere collider in the forward direction of each boid to check if they are about to hit a wall. If they are I add a component, that is basically just a tag, to them in order to separate the ones need to change direction and ones that don’t. The tag is removed at the end of the system.

Here’s where my issue comes in. I am using a function to get an evenly distributed number of 300 points on a unit sphere, and then casting colliders in those directions for each boid with the tag mentioned above, and find the first position that doesn’t collide with anything to move towards. However I am not sure if my linear algebra is correct or if it is an issue of not understanding how to properly use spatial queries, but the boids are often unable to find a free position to change direction towards. I’ve tried multiple ways of casting colliders, and none have been able to keep a boid inside a box, the entity is always able to eventually pass through a wall.

I would like to be able to see the colliders being cast, so I can try to debug where the issue in my program is, but I’ve only seen gizmos draw out colliders in samples, and I am inheriting from SystemBase and not MonoBehaviour. Is there a simple way to do this inside a SystemBase class?

Code Below

public struct MultiColliderCastJob : IJobParallelFor
    {
        [ReadOnly] public NativeArray<ColliderCastInput> inputs;
        [ReadOnly] public PhysicsWorld World;
        public NativeArray<float3> results;

        public void Execute(int index)
        {
            float goldenRatio = (1 + math.sqrt(5)) / 2;
            float angleIncrement = math.PI * 2 * goldenRatio;

            results[index] = float3.zero;
            float4x4 trs = float4x4.TRS(inputs[index].Start, inputs[index].Orientation, new float3(0.5f));

            for (int i = 0; i < 300; i++)
            {
                float t = i / 300f;
                float inclination = math.acos(1 - 2 * t);
                float azimuth = angleIncrement * i;

                float3 direction = new float3(math.sin(inclination) * math.cos(azimuth), math.sin(inclination) * math.sin(azimuth), math.cos(inclination));
                float3 point = math.transform(trs, direction)*5;

                ColliderCastInput colliderCastInput = new ColliderCastInput
                {
                    Collider = inputs[index].Collider,
                    Orientation = inputs[index].Orientation,
                    Start = inputs[index].Start,
                    End = point
                };

                World.CastCollider(colliderCastInput, out ColliderCastHit hit);
                if (hit.Entity.Equals(Entity.Null))
                {
                    results[index] = math.transform(trs, direction);
                    break;
                }
            }
        }
    }

The above code was my first attempt, where I made a job that would cast colliders for each boid that had a tag placed on it, with the 300 uniform directions, until one did not hit anything. Then I would record that direction in a reference array to update the direction of the boid in a later job. This didn’t work and I would often get entities getting pass the loop of 300 points without finding any open positions.

I then decided I would do a batch collider cast job, with 300 inputs for each uniform direction per boid. I also used a blobAsset to hold the 300 uniform points, so they didn’t have to be calculated at runtime. Then I would have another job cycle through each of the 300 ColliderCastHits to find a free position, but this had the same problem as the first.

Entities.ForEach((ref ObstacleVectors obsVectors, in int entityInQueryIndex, in Translation pos, in Rotation rot, in avoidObstacleTag tag) =>
        {
            int index = entityInQueryIndex * 300;
            ref ObstacleVectorsBlobAsset directions = ref obsVectors.blobAsset.Value;
            float4x4 trs = float4x4.TRS(pos.Value, rot.Value, new float3(0.5f));
            for (int i=0; i < directions.vectorsArray.Length; i++)
            {
                ccObsInputs[index+i] = new ColliderCastInput
                {
                    Collider = (Collider*)Collider.GetUnsafePtr(),
                    Orientation = rot.Value,
                    Start = pos.Value,
                    End = math.transform(trs, directions.vectorsArray[i])*3
                };
            }
        }).ScheduleParallel(Dependency).Complete();

        ScheduleBatchColliderCast(world, ccObsInputs, ref ccObsHits).Complete();

        Entities.WithReadOnly(ccObsHits).WithReadOnly(ccObsInputs).ForEach((Entity entity, ref accelerationObstacles obs, in BoidData bData, in Translation pos, in Rotation rot, in moveData mData, in int entityInQueryIndex, in avoidObstacleTag tag) =>
        {
            int index = entityInQueryIndex * 300;
            float3 moveTowards = float3.zero;
            for (int i = 0; i < 300; i++)
            {
                if (ccObsHits[index+i].Entity.Equals(Entity.Null))
                {
                    moveTowards = ccObsInputs[index + i].End;    
                    break;
                }
            }
            obs.acceleration += math.clamp(math.normalizesafe(moveTowards) * bData.maxSpeed - mData.velocity, -bData.maxTurnSpeed, bData.maxTurnSpeed) * 10f;
            ecb.RemoveComponent<avoidObstacleTag>(entityInQueryIndex, entity);
        }).ScheduleParallel(Dependency).Complete();

At this point, I am unsure what exactly to look into to see where my problem is. I think my use of the transform-rotation-scale matrix may be wonky, as I’ve never used it before, but it seems to be using all the right data. I also feel like having a loop inside the foreach loop is a pretty inefficient way of finding results, but I am unsure of another way to do it.

I have the rest of my code up on github as well at: GitHub - WHellhaus/Boids-DOTS: An implementation of the Boids simulation model created by Craig Reynolds using Unity's Data Oriented Technology Stack

Thanks for any responses

You might want to take a look at QueryTester.cs in the samples project, we do some collider cast debug display in there, but not from SystemBase. Take a look and let me know if it answers at least some questions.

I was using that code as reference for a lot of this, but I noticed a lot of the debugging was done using Gizmos which I wasn’t aware of any way to draw those easily with SystemBase. Although I just found this thread: https://discussions.unity.com/t/744996 so I’m gonna try that

2 Likes