Search Unity

idea for a new Physics.Raycast() overload

Discussion in 'Scripting' started by ScottHawkins, Feb 1, 2021.

  1. ScottHawkins

    ScottHawkins

    Joined:
    Jan 30, 2015
    Posts:
    10
    I don't know if this is the right forum to put this in, but I had an idea for something that would be nice to add to the Unity scripting API.

    Usually, Physics.RaycastAll() and Physics.RaycastNonAlloc() are used in situations where you want to use something other than layers or triggers to determine whether certain objects can be hit by a raycast. This works, but it means you have to allocate an array of RaycastHit objects. If you use Physics.RaycastAll(), the downside is that you're generating garbage in memory. If you use Physics.RaycastNonAlloc(), the downside is that you might not get every object hit by the raycast, if the array you started with isn't big enough to hold all the results.

    It would be great if the scripting API provided something like this:

    public static bool Raycast(Vector3 origin, Vector3 direction, out RaycastHit hitInfo, float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction, Func<RaycastHit, bool> canObjectBeHit)

    The last parameter would be a callback function that takes a RaycastHit as an input parameter and returns a bool. The Raycast code could call this for every object that it detects along the path of the array, and it could ignore any objects for which the callback function returned false. It would only need to keep track of the object that is closest to the source of the ray, so it wouldn't have to allocate an array/collection to store everything.

    A similar overload could be made available for methods like Physics.BoxCast(), Physics.SphereCast(), etc.
     
    Last edited: Feb 1, 2021
    MinhocaNice likes this.
  2. bobisgod234

    bobisgod234

    Joined:
    Nov 15, 2016
    Posts:
    1,042
    What would such a function do?

    I don't see how you couldn't essentially solve this problem by simply allocating a very large buffer (thousands long).
     
  3. ScottHawkins

    ScottHawkins

    Joined:
    Jan 30, 2015
    Posts:
    10
    It would do the same thing as Physics.Raycast() today, except you would have a way of controlling which objects the raycast ignores. Since it would use a callback function, such as a Func<RaycastHit, bool>, you could use whatever rules you want to decide whether any given object should be ignored by the raycast.

    The idea is that this would solve the problem without requiring you to allocate a huge buffer.
     
    Last edited: Feb 1, 2021
  4. bobisgod234

    bobisgod234

    Joined:
    Nov 15, 2016
    Posts:
    1,042
    What is the issue with allocating a large buffer?
     
    Kurt-Dekker likes this.
  5. ScottHawkins

    ScottHawkins

    Joined:
    Jan 30, 2015
    Posts:
    10
    I never meant to imply that a large buffer wouldn't work. This is not a bug report. I'm just saying, it would be nicer/slicker if you didn't HAVE to allocate a large buffer. Why would you want to waste code and memory dealing with a buffer if the API provided an option that didn't need one?
     
  6. bobisgod234

    bobisgod234

    Joined:
    Nov 15, 2016
    Posts:
    1,042
    How big a buffer are we taking here?

    It would seem pretty easy to put together a static function that would do this. Use the regular RaycastNonAlloc, filter into another buffer based on Func, and sort the results.

    I only say this because I am not sure if such a function would add enough value to be worth adding to the API (happy to be proven otherwise).
     
  7. ScottHawkins

    ScottHawkins

    Joined:
    Jan 30, 2015
    Posts:
    10
    I already have a similar static helper method (no need for a second buffer or for sorting -- just loop through once and keep track of the result with the minimum distance where the callback Func returns true), but I guess I regard it as hackish and silly? A method overload in the API would be much cleaner...
     
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,757
    I ask myself this every morning right after my first sip of coffee.
     
    JoNax97 and bobisgod234 like this.
  9. bobisgod234

    bobisgod234

    Joined:
    Nov 15, 2016
    Posts:
    1,042
    That doesn't sound hackish or silly to me at all (and is a better solution than my idea). If you made it an extension function, it would almost look like part of the API.

    I just don't know about adding a function callback like that to a physics call. Either it would have to be invoked from native physics code while doing the raycast (which I suspect might have a significant performance impact - especially if we are talking about casting against lots and lots of objects), or Unity would do essentially what you are doing internally.
     
  10. ScottHawkins

    ScottHawkins

    Joined:
    Jan 30, 2015
    Posts:
    10
    Alas, I don't think C# lets you make static extension methods. That would've been nice.

    Can the native physics code not call C# methods? I don't know the answer.
     
  11. bobisgod234

    bobisgod234

    Joined:
    Nov 15, 2016
    Posts:
    1,042
    It seems C# can't do static extension methods, so nevermind that then :oops:.

    Native physics code should I think be able to invoke C# methods. It's more performance I would be concerned about. Those kinds of functions tend to be written to be highly optimised. I am not sure what the performance cost of invoking a C# function is from native code, but I would suspect that it could be a problem if done in a tight loop. All I can do is speculate though.
     
  12. ScottHawkins

    ScottHawkins

    Joined:
    Jan 30, 2015
    Posts:
    10
    This is the basic gist of my implementation. In my own code, I've got overloads for the various forms of BoxCast, CapsuleCast, Raycast, and SphereCast, but for simplicity, I'll just show one version of Raycast here:

    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. public static class PhysicsUtilities
    5. {
    6.     private static RaycastHit[] resultBuffer;
    7.     private const int defaultBufferSize = 32;
    8.     private static readonly object resultBufferLock = new object();
    9.  
    10.     public static void EnsureBufferSize(int size = defaultBufferSize)
    11.     {
    12.         if (resultBuffer == null || resultBuffer.Length < size)
    13.         {
    14.             resultBuffer = new RaycastHit[size];
    15.         }
    16.     }
    17.  
    18.     public static int GetBufferSize()
    19.     {
    20.         return resultBuffer.Length;
    21.     }
    22.  
    23.     private static bool GetClosestHit(int resultCount, Func<RaycastHit, bool> canObjectBeHit, out RaycastHit hitInfo)
    24.     {
    25.         int hitIndex = -1;
    26.         float minDistance = Mathf.Infinity;
    27.  
    28.         for (int i = 0; i < resultCount; i++)
    29.         {
    30.             if (resultBuffer[i].distance < minDistance && canObjectBeHit(resultBuffer[i]))
    31.             {
    32.                 minDistance = resultBuffer[i].distance;
    33.                 hitIndex = i;
    34.             }
    35.         }
    36.  
    37.         if (hitIndex == -1)
    38.         {
    39.             hitInfo = new RaycastHit();
    40.             return false;
    41.         }
    42.         else
    43.         {
    44.             hitInfo = resultBuffer[hitIndex];
    45.             return true;
    46.         }
    47.     }
    48.  
    49.     public static bool Raycast(Vector3 origin, Vector3 direction, out RaycastHit hitInfo, float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction, Func<RaycastHit, bool> canObjectBeHit)
    50.     {
    51.         lock (resultBufferLock)
    52.         {
    53.             EnsureBufferSize();
    54.  
    55.             int resultCount = Physics.RaycastNonAlloc(origin, direction, resultBuffer, maxDistance, layerMask, queryTriggerInteraction);
    56.  
    57.             while (resultCount >= resultBuffer.Length)
    58.             {
    59.                 EnsureBufferSize(resultBuffer.Length * 2); //Make sure the buffer is big enough to store the results.
    60.                 resultCount = Physics.RaycastNonAlloc(origin, direction, resultBuffer, maxDistance, layerMask, queryTriggerInteraction);
    61.             }
    62.  
    63.             return GetClosestHit(resultCount, canObjectBeHit, out hitInfo);
    64.         }
    65.     }
    66. }
     
    Last edited: Feb 1, 2021
  13. JohnDoe2001

    JohnDoe2001

    Joined:
    Feb 20, 2018
    Posts:
    11
    Thanks for sharing this!
     
  14. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,998
    I've got a hand-written function that does that but I never use it. The last input -- the "is this a valid object" delegate -- is always null. I think at one point I used it to only hit objects with a particular script on them (
    s=>s.parent!=null && s.parent.GetComponent<monster>()!=null
    ) but then I realized it was easier and safer (and ran faster) to simply set the layers how I wanted. I mean, you have to set the layers correctly anyway, why not make that the only step?
     
  15. ScottHawkins

    ScottHawkins

    Joined:
    Jan 30, 2015
    Posts:
    10
    If you can do what you need to using layers alone, that's probably best.
     
  16. ScottHawkins

    ScottHawkins

    Joined:
    Jan 30, 2015
    Posts:
    10
    Here's the complete code file if anyone wants all the different overloads for BoxCast, CapsuleCast, Raycast, and SphereCast. I haven't tested it very much, so if you find any bugs, please reply and let me know! Thanks.
     

    Attached Files: