Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

RaycastCommands and simple line of sight

Discussion in 'Entity Component System' started by JakHussain, May 27, 2020.

  1. JakHussain

    JakHussain

    Joined:
    Oct 20, 2016
    Posts:
    318
    I have the following method which checks which of a collection of points can see each other, resulting in a 2d matrix of results which I flatten into a single 1D array:

    Code (CSharp):
    1.    
    2.     private void LineOfSight ()
    3.     {
    4.         NativeArray<RaycastCommand> commands = new NativeArray<RaycastCommand> (MatrixSize, Allocator.TempJob);
    5.         NativeArray<RaycastHit> results = new NativeArray<RaycastHit> (MatrixSize, Allocator.TempJob);
    6.  
    7.         JobHandle handle = new SetUpRaycasts
    8.         {
    9.             points = this.points,
    10.  
    11.             raycastCommands = commands
    12.         }.Schedule (MatrixSize, 1);
    13.  
    14.         RaycastCommand.ScheduleBatch (commands, results, 1, handle).Complete ();
    15.  
    16.         flattenedLineOfSightMatrix = new NativeArray<bool> (MatrixSize, Allocator.Persistent);
    17.  
    18.         for (int i = 0; i < commands.Length; i++)
    19.         {
    20.             flattenedLineOfSightMatrix[i] = results[i].collider != null;
    21.         }
    22.  
    23.         commands.Dispose ();
    24.         results.Dispose ();
    25.     }
    26.  
    The unity docs for raycast commands tells us that if you want to know if a ray hit something or not, you need to check if a given result's collider field is null or not. since a raycasthit is a value type BUT isn't blittable, I can't use the results nativearray in another job to extract the booleans and assign them into my nativearray of bools called flattenedLineOfSightMatrix.

    This isn't particularly scalable so I was wondering if anyone knew of a faster way of getting just plain booleans of my results so that the code can quickly move on to other operations.

    Ideally, RaycastCommand.ScheduleBatch would just have a simple overload that spat out a nativearray of bools for the results as an optional alternative to getting back the whole raycasthit object.

    Any ideas?
     
  2. GeorgeAdamon

    GeorgeAdamon

    Joined:
    May 31, 2017
    Posts:
    48
    Hey Jak,
    have you tested Lotte's solution here?

    https://github.com/LotteMakesStuff/SimplePhysicsDemo/blob/master/Assets/SimpleJobifiedPhysics.cs

    Seems like she can access RaycastHit data just fine in a job.
     
  3. GeorgeAdamon

    GeorgeAdamon

    Joined:
    May 31, 2017
    Posts:
    48
    Having a peek at the
    RaycastHit
    source, it seems to contain plain unmanaged types
    Code (CSharp):
    1.  public struct RaycastHit
    2.   {
    3.     [NativeName("point")]
    4.     internal Vector3 m_Point;
    5.     [NativeName("normal")]
    6.     internal Vector3 m_Normal;
    7.     [NativeName("faceID")]
    8.     internal uint m_FaceID;
    9.     [NativeName("distance")]
    10.     internal float m_Distance;
    11.     [NativeName("uv")]
    12.     internal Vector2 m_UV;
    13.     [NativeName("collider")]
    14.     internal int m_Collider;
    15.  
    so I can't see why the JobSystem would complain about it.
     
    JakHussain likes this.
  4. JakHussain

    JakHussain

    Joined:
    Oct 20, 2016
    Posts:
    318
    Nice so that means that a NativeArray<RaycastHit> should also work in a bursted job as well! I guess the collider reference must be a property that's indexed via that internal integer.

    Thanks for the help!
     
  5. GeorgeAdamon

    GeorgeAdamon

    Joined:
    May 31, 2017
    Posts:
    48
    Yes, this happens internally any time you call the
    collider
    property:
    Code (CSharp):
    1.     /// <summary>
    2.     ///   <para>The Collider that was hit.</para>
    3.     /// </summary>
    4.     public Collider collider
    5.     {
    6.       get
    7.       {
    8.         return Object.FindObjectFromInstanceID(this.m_Collider) as Collider;
    9.       }
    10.     }
     
  6. UnconventionalWarfare

    UnconventionalWarfare

    Joined:
    Jul 11, 2019
    Posts:
    23
    I wrote a high-performance field of view visualization a few weeks ago. You cannot access hit.collider safely, but what you can do is check whether the normal vector of the hit is a zero vector. It's left uninitialized for raycasts that didn't hit anything.

    Btw:
    • RaycastCommand only returns the first hit, even though its API suggests otherwise (no there's no way to work around this as of this day)
    • The hit distance is left at 0 for missed rays, whereas the non-jobs Raycast API sets it to 1 for missed rays
     
  7. JakHussain

    JakHussain

    Joined:
    Oct 20, 2016
    Posts:
    318
    @UnconventionalWarfare I was about to post a follow-up question but your trick for checking if the normal is zero worked like a charm thanks!
     
  8. UnconventionalWarfare

    UnconventionalWarfare

    Joined:
    Jul 11, 2019
    Posts:
    23
    @JakHussain Are you using ECS or MonoBehavior with jobs? You really want to avoid the Schedule().Complete() pattern, because it wastes performance. In the case of simple jobs, the forced synchronization that happens when you call Complete() on a job that hasn't been completed yet, will take more CPU time than the actual calculations.
    If you use MonoBehavior with jobs, I can share a script I wrote the other day to make it easy to avoid that pattern.
     
    JakHussain likes this.
  9. JakHussain

    JakHussain

    Joined:
    Oct 20, 2016
    Posts:
    318
    @UnconventionalWarfare yeah it's a monobehaviour project that's why I'm relying on raycast commands. If you could share a script with a short description on how you're getting those performance gains I would be super grateful!
     
  10. UnconventionalWarfare

    UnconventionalWarfare

    Joined:
    Jul 11, 2019
    Posts:
    23
    Here you go.

    The problem is that coordination between threads is quite inefficient. When you force a job to complete, you pay that cost in the main thread. So you want to call that function after the job has already completed, at which point it'll become basically free.

    To do that, you have to call Schedule() as early as possible in the frame and Complete() as late as you can. This maximizes the amount of time Unity has to finish the job on its own.

    My script uses the PlayerLoop API to add additional callbacks to the player loop.
    • BeforeUpdateTick: Called before the first MonoBehaviour has its Update() function called
    • AfterUpdateTick: Called after the last MonoBehaviour has its Update() funciton called
    • BeforeLateUpdateTick: Called before the first MonoBehaviour has its LateUpdate() funciton called
    • AfterLateUpdateTick: Called after the last MonoBehaviour has its LateUpdate() funciton called
    With these, you can make your script tick multiple times during a single frame. I have a script that schedules a job in BeforeUpdate and completes it in AfterLateUpdate. This means all of the MonoBehaviours in the scene do Update() and LateUpdate() in between Schedule() and Complete(). Here's what this looks like:

    Annotation 2020-05-28 211804.png

    How to use:
    1. Implement the desired interfaces (e.g. ITickBeforeUpdate)
      1. Implement the tick functions (use explicit interface implementation for safety and to be able to implement more than one)
      2. The name properties of the interfaces are what you see in the profiler
    2. Call the ExtraTicks.Add...() functions to register your script to tick
    Important:
    1. MonoBehaviour.enabled is ignored; use an if(enabled) to handle skipping logic yourself or use ExtraTicks.Remove...() to stop a script from ticking entirely
    2. Be sure to manually remove scripts from the ticking callbacks when they are destroyed/disabled
     

    Attached Files:

    vildauget and brunocoimbra like this.