Search Unity

[SOLVED] Collision processing only working when archtype is the same

Discussion in 'Entity Component System' started by SKCode, Feb 28, 2019.

  1. SKCode

    SKCode

    Joined:
    Mar 3, 2017
    Posts:
    20
    Hey all!

    UPDATE: Solved thanks to the discussion in this thread. Link to post in this thread that solves the problem: Post #10

    I'm contiuning my work on https://github.com/skhamis/Unity-ECS-RTS and i'm finally working on collisions. I am currently using AABB and running into a really weird problem...the code below only "works" (Debug.Log("collision detected")) when two of my entities are either both selected or both not selected.

    Code (CSharp):
    1. using UnityEngine;
    2. using Unity.Burst;
    3. using Unity.Entities;
    4. using Unity.Jobs;
    5. using Unity.Mathematics;
    6. using Unity.Collections;
    7.  
    8. public class AABBCollisionSystem : JobComponentSystem
    9. {
    10.     //[BurstCompile]
    11.     public struct AABBCollisionJob : IJobChunk
    12.     {
    13.         //Must be marked readonly because it is marked as ReadOnly in the OnUpdate
    14.         [ReadOnly] public ArchetypeChunkComponentType<AABB> Colliders;
    15.  
    16.         public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
    17.         {
    18.             var chunkColliders = chunk.GetNativeArray(Colliders);
    19.             var instanceCount = chunk.Count;
    20.  
    21.             for (int i = 0; i < instanceCount; i++)
    22.             {
    23.                 for(int j = 0; j < instanceCount; j++)
    24.                 {
    25.                     if (i != j)
    26.                     {
    27.                         if(RTSPhysics.Intersect(chunkColliders[i], chunkColliders[j]))
    28.                         {
    29.                             Debug.Log("COLLISION OCCURED");
    30.                         }
    31.                    
    32.                     }
    33.                
    34.                 }
    35.             }
    36.         }
    37.     }
    38.  
    39.     ComponentGroup m_AABBGroup;
    40.  
    41.     protected override void OnCreateManager()
    42.     {
    43.         var query = new EntityArchetypeQuery
    44.         {
    45.             All = new ComponentType[] { typeof(AABB) }
    46.         };
    47.         m_AABBGroup = GetComponentGroup(query);
    48.     }
    49.  
    50.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    51.     {
    52.         var colliders = GetArchetypeChunkComponentType<AABB>(true);
    53.  
    54.         var aabbCollisionJob = new AABBCollisionJob
    55.         {
    56.             Colliders = colliders,
    57.         };
    58.         return aabbCollisionJob.Schedule(m_AABBGroup, inputDeps);
    59.     }
    60. }
    Code (CSharp):
    1.     public static BlittableBool Intersect(AABB box1, AABB box2)
    2.     {
    3.         var collide = (box1.min.x <= box2.max.x && box1.max.x >= box2.min.x) &&
    4.                (box1.min.y <= box2.max.y && box1.max.y >= box2.min.y) &&
    5.                (box1.min.z <= box2.max.z && box1.max.z >= box2.min.z);
    6.  
    7.         return collide;
    8.     }

    So three scenarios which the above code works are:

    (1) If I put two blocks together then make sure both are deselected = Collision successfully detected
    (2) If I put the two blocks close together, then click to select both of them == collision successfully detected
    (3) If I have one entity selected then right click it to go on a path towards an un-selected entity, then un-select the first one == collision successfully detected


    So my collision is indeed working however after hours of debugging the only thing I can think of is that for some reason if it's a different archtype (basically only one has PlayerUnitSelect) it doesn't work. Any thoughts? Appreciate it!
     
    Last edited: Feb 28, 2019
  2. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    This is exactly what is happened.

    Your data is broken up into chunks.

    Lets say you have 5 components

    ABC only have a AABB component
    DE have a AABB and a lets call it X component.

    So now you have 2 chunks

    First chunk is ABC (Archetype {AABB})
    Second chunk is DE (Archetype {AABB, X})

    Your job is only checking each chunk against the same chunk

    For the first chunk of the job you check
    A v A, then A v B then AvC, BvA, BvB, BvC, CvA, CvB, CvC

    Then the second chunk you check
    DvD, DvE, EvD, EvE

    And that's it. Your logic never has different chunks do intersect detection against each other.
     
    SKCode likes this.
  3. SKCode

    SKCode

    Joined:
    Mar 3, 2017
    Posts:
    20
    Ah that completely makes sense! I'll have to rethink how I want to re-structure some stuff then to make it much more dynamic but that seems to be exactly what's happening. Thanks a ton tertle!
     
  4. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    I think you should have 2 problems,
    1. the one you already mentioned with different Archetypes -> chunks
    2. even if you only have one Archetype but more entities that fit in 1 chunk, you will not detect all collisions

    The simplest (not necessarily the fastest) solution is for you to copy all colliders to a native array and then iterate over it with a parallel job.
     
  5. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    Use NativeMultiHashMap for dividing colliders to quad\oc-tree and for collision detection get from NMHM only colliders which in same leaf (by current collider pos) for reduce amount of unnecessary checks. Prefill this NMHM in parallel bursted job (which extremelly fast), and pass to IJobParallelFor which iterates colliders and detect collision between them and from NMHM.
     
    Sibz9000 likes this.
  6. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    Sure, you should have a broad phase if you have many entities.

    @eizenhorn : do you have a collision system up and running. I have experimented quite a bit 2d aabb and would be interested how you iterate over the NMHM values in a leaf (I use a spatial grid but it’s the same concept)
     
  7. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    TryGetFirstValue/TryGetNextValue by key calculated from current collider pos.
     
  8. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    Thx - yes I know how to do it but in my tests buffers where outperforming NMHM.

    To be honest for NMHM, I copied values to array before checking collisions - never tried to just iterate the NMHM directly
     
  9. SKCode

    SKCode

    Joined:
    Mar 3, 2017
    Posts:
    20
    Loving the discussion! I am a bit against going for implementing any kind of spatial partitioning right now as I'm basically just doing my naive/simple approach to get some simple collision detection going so I could move on to the RTS logic of my tutorial series. I believe Unity will most likely have their own collision system that would be implemented better than any concoction I could whip up.

    I do like the approach of just copying all into a NativeArray and a bursted job to speed up any inefficiencies (though it'll still be O(n2)). I'll update this post with my updated code if/when I get it working!
     
  10. SKCode

    SKCode

    Joined:
    Mar 3, 2017
    Posts:
    20
    Got it working! Updated code:
    Code (CSharp):
    1. using UnityEngine;
    2. using Unity.Burst;
    3. using Unity.Entities;
    4. using Unity.Jobs;
    5. using Unity.Collections;
    6.  
    7. public class AABBCollisionSystem : JobComponentSystem
    8. {
    9.     [BurstCompile]
    10.     public struct AABBCollisionJob : IJobChunk
    11.     {
    12.         [ReadOnly] public NativeArray<AABB> Colliders;
    13.  
    14.         public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
    15.         {
    16.             for (int i = 0; i < Colliders.Length; i++)
    17.             {
    18.                 for (int j = 0; j < Colliders.Length; j++)
    19.                 {
    20.                     if (i != j)
    21.                     {
    22.                         if (RTSPhysics.Intersect(Colliders[i], Colliders[j]))
    23.                         {
    24.                             //Debug.Log("COLLISION OCCURED");
    25.                         }
    26.                     }
    27.                 }
    28.             }
    29.         }
    30.     }
    31.  
    32.     ComponentGroup m_AABBGroup;
    33.  
    34.     protected override void OnCreateManager()
    35.     {
    36.         var query = new EntityArchetypeQuery
    37.         {
    38.             All = new ComponentType[] { typeof(AABB) }
    39.         };
    40.         m_AABBGroup = GetComponentGroup(query);
    41.     }
    42.  
    43.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    44.     {
    45.         var colliders = m_AABBGroup.ToComponentDataArray<AABB>(Allocator.TempJob);
    46.         var aabbCollisionJob = new AABBCollisionJob
    47.         {
    48.             Colliders = colliders,
    49.         };
    50.         var collisionJobHandle = aabbCollisionJob.Schedule(m_AABBGroup, inputDeps);
    51.         collisionJobHandle.Complete();
    52.  
    53.         colliders.Dispose();
    54.         return collisionJobHandle;
    55.     }
    56. }
    Bursting this job actually has huge performance gains - below is a small sample of 225 entities



    20ms -> 0.5ms
     
  11. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    You should use ijobparallelfor and you do not have to check collisions twice.
    i = 0 to <length-1
    j = i+1 to <length

    You can also let i go to length in the parallelforjob as you will exit in th j loop when j is not < length
     
  12. SKCode

    SKCode

    Joined:
    Mar 3, 2017
    Posts:
    20
    Darn, of course I shouldn't be just starting from 0 again! Thanks for the suggestion sngdan!

    Updated code:

    Code (CSharp):
    1. using UnityEngine;
    2. using Unity.Burst;
    3. using Unity.Entities;
    4. using Unity.Jobs;
    5. using Unity.Collections;
    6.  
    7. public class AABBCollisionSystem : JobComponentSystem
    8. {
    9.     [BurstCompile]
    10.     public struct AABBCollisionJob : IJobParallelFor
    11.     {
    12.         [ReadOnly] public NativeArray<AABB> Colliders;
    13.  
    14.         public void Execute(int i)
    15.         {
    16.             for (int j = i + 1; j < Colliders.Length; j++)
    17.             {
    18.                 if (RTSPhysics.Intersect(Colliders[i], Colliders[j]))
    19.                 {
    20.                     //Debug.Log("Collision Detected");
    21.                 }
    22.             }
    23.         }
    24.     }
    25.  
    26.     ComponentGroup m_AABBGroup;
    27.  
    28.     protected override void OnCreateManager()
    29.     {
    30.         var query = new EntityArchetypeQuery
    31.         {
    32.             All = new ComponentType[] { typeof(AABB) }
    33.         };
    34.         m_AABBGroup = GetComponentGroup(query);
    35.     }
    36.  
    37.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    38.     {
    39.         var colliders = m_AABBGroup.ToComponentDataArray<AABB>(Allocator.TempJob);
    40.         var aabbCollisionJob = new AABBCollisionJob
    41.         {
    42.             Colliders = colliders,
    43.         };
    44.         var collisionJobHandle = aabbCollisionJob.Schedule(colliders.Length, 32);
    45.         collisionJobHandle.Complete();
    46.  
    47.         colliders.Dispose();
    48.         return collisionJobHandle;
    49.     }
    50. }
     
  13. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    yep, that's more like it :)

    the last piece of optimization I see is to pull Colliders[ i ] out of the loop.
    i.e.
    var ColliderI = Colliders [ i ];
    for (int j.....) {....}

    or you can go a step further and pull some of the Intersect function for ColliderI out into the Execute but that makes it less nice to read

    edit: does not like [ i ] without space, all cursive now....i hope you still get what i meant
     
    SKCode likes this.
  14. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    you should use
    [code=CSharp][/code]
    blocks when posting code on the forums
     
  15. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    I do & usually put in spoiler - it’s just when using the phone that I don’t, but yeah, it’s ugly and messy.
     
  16. schaefsky

    schaefsky

    Joined:
    Aug 11, 2013
    Posts:
    90
    Hello there, since ComponentDataArray is marked as deprecated (apparently indexing it is slow), I cobbeled out a version of the collision detection without it.

    https://github.com/Unity-Technologi...b/master/Documentation~/ecs_best_practices.md

    Interestingly it is about 5 times faster on my machine than your version (~0.2 to ~1.0ms) without Burst, but pretty much the same with Burst (~0.10 to ~0.14ms).

    Updated March 2nd for some bugfixing thanks to aufhel. Also thanks to sngdan and Nicolas Clement (youtube) since I used some of their ideas in the original code. And of course skhamis.
    Code (CSharp):
    1. using UnityEngine;
    2. using Unity.Burst;
    3. using Unity.Entities;
    4. using Unity.Jobs;
    5. using Unity.Collections;
    6. public class AABBCollisionSystemEx : JobComponentSystem
    7. {
    8.     [BurstCompile]
    9.     public struct AABBCollisionJobEx : IJobParallelFor
    10.     {
    11.         [ReadOnly] [DeallocateOnJobCompletion] public NativeArray<ArchetypeChunk> Chunks;
    12.         [ReadOnly] public ArchetypeChunkComponentType<AABB> AABBType;
    13.         //public void Execute(int chunkIndex)
    14.         //{
    15.         //    var chunk = Chunks[chunkIndex];
    16.         //    var chunkAABB = chunk.GetNativeArray(AABBType);
    17.         //    for (int i = 0; i < chunk.Count; i++)
    18.         //    {
    19.         //        var c = chunkAABB[i];
    20.         //        for (int k = i + 1; k < chunk.Count; k++)
    21.         //        {
    22.         //            if (RTSPhysics.Intersect(c, chunkAABB[k]))
    23.         //            {
    24.         //                //Debug.Log("Collision Detected");
    25.         //            }
    26.         //        }
    27.         //    }
    28.         //    for (int otherChunkIndex = chunkIndex + 1; otherChunkIndex < Chunks.Length; otherChunkIndex++)
    29.         //    {
    30.         //        var otherChunk = Chunks[otherChunkIndex];
    31.         //        var otherChunkAABB = otherChunk.GetNativeArray(AABBType);
    32.         //        for (int i = 0; i < chunk.Count; i++)
    33.         //        {
    34.         //            var c = chunkAABB[i];
    35.         //            for (int k = 0; k < otherChunk.Count; k++)
    36.         //            {
    37.         //                if (RTSPhysics.Intersect(c, otherChunkAABB[k]))
    38.         //                {
    39.         //                    //Debug.Log("Collision Detected");
    40.         //                }
    41.         //            }
    42.         //        }
    43.         //    }
    44.         //}
    45.         public void Execute(int chunkIndex)
    46.         {
    47.             var chunk = Chunks[chunkIndex];
    48.             var chunkAABB = chunk.GetNativeArray(AABBType);
    49.             for (int i = 0; i < chunk.Count; i++)
    50.             {
    51.                 var c = chunkAABB[i];
    52.                 for (int k = i + 1; k < chunk.Count; k++)
    53.                 {
    54.                     if (RTSPhysics.Intersect(c, chunkAABB[k]))
    55.                     {
    56.                         //Debug.Log("Collision Detected");
    57.                     }
    58.                 }
    59.                 // my understanding of accessing chunks would normaly let me swich the postion of this loop and the "i"-loop
    60.                 // however I can not detect a performance difference on my machine with or without Burst for more than 10.000 cubes
    61.                 // and the code is shorter and better readable, other version above
    62.                 for (int otherChunkIndex = chunkIndex + 1; otherChunkIndex < Chunks.Length; otherChunkIndex++)
    63.                 {
    64.                     var otherChunk = Chunks[otherChunkIndex];
    65.                     var otherChunkAABB = otherChunk.GetNativeArray(AABBType);
    66.                     for (int k = 0; k < otherChunk.Count; k++)
    67.                     {
    68.                         if (RTSPhysics.Intersect(c, otherChunkAABB[k]))
    69.                         {
    70.                             //Debug.Log("Collision Detected");
    71.                         }
    72.                     }
    73.                 }
    74.             }
    75.         }
    76.     }
    77.     ComponentGroup m_AABBGroup;
    78.     protected override void OnCreateManager()
    79.     {
    80.         var query = new EntityArchetypeQuery
    81.         {
    82.             All = new ComponentType[] { typeof(AABB) }
    83.         };
    84.         m_AABBGroup = GetComponentGroup(query);
    85.     }
    86.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    87.     {
    88.         var aABBType = GetArchetypeChunkComponentType<AABB>(true);
    89.         var chunks = m_AABBGroup.CreateArchetypeChunkArray(Allocator.TempJob);
    90.         var aabbCollisionJobEx = new AABBCollisionJobEx
    91.         {
    92.             Chunks = chunks,
    93.             AABBType = aABBType
    94.         };
    95.         // batch count of 8 seemed to be good on my machine, altough there is hardly any difference from1-64 for me (~0.01ms)
    96.         var aabbCollisionJobExHandle = aabbCollisionJobEx.Schedule(chunks.Length, 8, inputDeps);
    97.         return aabbCollisionJobExHandle;
    98.     }
    99. }
    I will try a version with filtering for moving objects tomorrow.

    Unfortunately I have backpain at the moment and can not sit for a longer period of time. I coded that lying down and on pain and muscle relaxing medication, so there might be some errors :D Debug output seemed ok though.
    Since pretty much all I can do is watching videos at the moment, a new upload from you soon would be very much appreciated!
    I am a Unity newbie (started a space shooter about 5 years ago but live intervened and have not done anything since then) and stumbled upon your ECS series and very much like it.
    I also blame any typos on the meds.
     
    Last edited: Mar 2, 2019
  17. SKCode

    SKCode

    Joined:
    Mar 3, 2017
    Posts:
    20
    Glad you like the series schaefsky! I did just get a new upload there though some of my other side projects have been getting my attention. I was actually saving the "upgrade" process for my next video where we cover upgrading to p24 and all the things we gotta change, etc. I like your code above and think it's a great solution but I hope to try simplify it to remove some of the developer overhead that comes with comparing all chunks with other chunks, etc.
     
    schaefsky likes this.
  18. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    @schaefsky

    This is the other (more involved) option, I suggested above - good, now there is an example of both.

    But let me bring to your attention that there is a difference between GetCDA (deprecated) and ToCDA (new API) - they do different things under the hood.

    With chunk iteration you avoid the memcopy, but the code is more complex (and there might be situations, where memcopy to native array is more efficient)


    Edit: I must have confused this thread with another similar thread, where I suggested the 2 approaches (a) chunk iteration and (b) toCDA
     
    Last edited: Mar 2, 2019
  19. schaefsky

    schaefsky

    Joined:
    Aug 11, 2013
    Posts:
    90
    Yeah I know but that was yesterday! Come on, no excuses :p

    Could you link me some documentation or other info on this? I am not able to find any, maybe I am too stupid
     
  20. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    I am not sure there is any documentation. There is a recent post though that clearly says whats deprecated and it's not NativeArray :)

    I looked at the source for ToCDA and it's basically a chunk iteration without the inner loop (it uses memcopy to block copy all elements of a chunk to the native array). I guess this is cleaner - you have the upfront memcopy cost but later you really operate on an array, whereas GetCDA only makes you think you are operating on an array, while you are not.

    When you sequentially go through the chunk (like your code above) the main/only benefit from ToCDA is that your own scripts stay neater (at the cost of speed i.e. memcopy) while with chunk iteration, you just get pointers to the data (no new allocation) - that's at least my understanding. But if you need to jump around in the data it's much easier to do so with an array (say sorting or so...)
     
    schaefsky likes this.
  21. auhfel

    auhfel

    Joined:
    Jul 5, 2015
    Posts:
    92
    I implemented your code mostly the same, I think you do have one bug though. Any of the entities in the first chunk don't test collisions with eachother. It didn't work at all for me until I made two different archetypes with colliders on them. I think it's an easy fix, just add a loop to iterate over the first chunk with itself.
     
    schaefsky likes this.
  22. schaefsky

    schaefsky

    Joined:
    Aug 11, 2013
    Posts:
    90
    Yes thanks, you are right. Thinking about it you are right that it can not work that way. Funny enough I could not replicate the collision not working because for 4 units it seems once I moved a unit the chunks were split in size 1 chunks. At the beginning without movement there was just one chunk and my code did simply nothing.
    Updated my post.
     
    auhfel likes this.
  23. auhfel

    auhfel

    Joined:
    Jul 5, 2015
    Posts:
    92
    Thanks for adding the code! I just got off work and I'm a bit drunk (foodservice/customerservice does that to you) but when I wake up and I'm sober I'll test your code, and if I'm feeling well enough I'll try to implement a broadphase for the code too,
     
  24. schaefsky

    schaefsky

    Joined:
    Aug 11, 2013
    Posts:
    90
    This will be great, I am still on meds, you will be hungover, great things could come from this!