Search Unity

RaycastNonAlloc sort collision from furthest to closest?

Discussion in 'Physics' started by Sluggy, Nov 25, 2018.

  1. Sluggy

    Sluggy

    Joined:
    Nov 27, 2012
    Posts:
    983
    So this has been an issue for a while now (since at least 2017.3 irrc) and it's getting quite cumbersome as it means I can never actually be sure that using RaycastNonAlloc with an RaycastHit[1] sized array will actually hit the thing I expect it to. As a result I often have to supply a much larger buffer than I need followed by a squared distance comparison to find the collision I'm actually interested in.

    Up until now, I had assumed this was just intentional behavior for optimization reasons, but I recently found a conversation here https://answers.unity.com/questions/1240670/raycastnonalloc-return-value-ordering.html, where @MelvMay suggest that this is not how RaycastNonAlloc works. Apparently, they should in fact be ordered from closest to furthest.

    I can confirm absolutely that sorting does not always work this way. It's been an issue that I've had to solve on many occasions now because I keep forgetting about it until some unexpected bug pops up due to lack of sorting functionality implemented in my own code.

    I've posted a video on youtube to demonstrate this bug manifested in my most recent feature. It's a fairly simple system of rendering blobw shadows by using Raycasts to detect where they should be placed. What I find most strange about this particular case is that you can clearly see the sorting changes order based on where the raycast strikes the box collider. This makes me think that sorting might somehow be based on the central pivot of the collider it is striking?

     
  2. https://docs.unity3d.com/ScriptReference/Physics.RaycastNonAlloc.html
    https://docs.unity3d.com/ScriptReference/Physics.RaycastAll.html

    BTW, do you mind if I ask why do you use this function at all? In the video you absolutely don't use the main feature of this solution: multiple hits. You only care about the closest hit, which the simple Physics.Raycast does with the out hit parameter. So why not use that?
     
    Last edited by a moderator: Nov 25, 2018
    sj631 and Sluggy like this.
  3. Sluggy

    Sluggy

    Joined:
    Nov 27, 2012
    Posts:
    983
    Indeed, as I stated before I just assumed that sorting order wasn't guaranteed - until I read that particular post on Unity Answers. Regardless of whether or not it is, it seems odd that the results returned wouldn't be ordered. I mean, if there is no ordering then the only way you could ever ensure that the correct object is found would be to provide an array that is the maximum size of objects you'd ever expect to be hit and then sort through it every time. That seems silly to me, especially when in ninety-nine percent of all cases I'm sure people would only want the very first thing that was hit. The closest thing. I suppose for the sake of optimization it might be necessary though.

    Really the whole thing might be moot though because...

    The reason I've been using the multi-hit version was due to the fact that in the past the single-hit version produced garbage for a time (Unless it was just me being a dummy and not profiling outside of the editor. It was a long time ago). Admittedly, I haven't tested it in a long time so I went ahead and did that now. It does indeed appear to be garbage-free again so yeah... now I feel a little silly. I guess I can go back to using that one again.
     
  4. I would always believe in the documentation more than in a guy on a site no matter what. Especially if the documentation explicit says something.

    Actually that's not odd at all. If you don't care about order, you get the list of things you hit. So you get away with less work. If you need order, you can order for yourself. Less work mean faster, more efficient application. I'm with Unity on this one.

    That's what the RayCast method is for. So it should not be repeated in the multi-hit version.
     
    SparrowGS likes this.
  5. Sluggy

    Sluggy

    Joined:
    Nov 27, 2012
    Posts:
    983
    Oh come now, I'd hardly call MelvMay 'a guy on a website' ;)

    In all seriousness though, it's a pretty common thing that the documentation says one thing and a Unity Technologies person says another. And there have been cases in the past where both have been wrong, outdated, or some way not congruent with reality (even if it's just due to a bug).

    All in the all, the problem appears to be solved so I'm glad for that. It only took about... *looks at watch* two years? But hey, at least it all works as the documentation says it should now! :D Thanks for hint. I might never have actually considered using the single-hit Raycast again had you not mentioned it. I honestly forgot it even existed hahaha!
     
  6. In this case, maybe both are right. MelvMay (pardon my ignorance I don't really know the 2D world, so I don't really know who we are talking about either) talked about Physics2D and in that case it is sorted: https://docs.unity3d.com/ScriptReference/Physics2D.RaycastAll.html

    But we were talking about (and also the video were in) 3D. Where it isn't sorted.
     
    Sluggy likes this.
  7. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,456
    All 2D physics queries that return distance are sorted, always have been. In that link you referred to it was talking explicitly about 2D physics. I don't think the 3D ones are as is stated in the docs but I could look through the source to find out.

    AFAIK the Physics.Raycast with the signature:
    Code (CSharp):
    1.  public static bool Raycast(Vector3 origin, Vector3 direction, out RaycastHit hitInfo, float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction);
    returns the first hit only.

    I'm curious, what took two years? AFAIK it's always been like the above but maybe my memory fails me.
     
    Last edited: Nov 25, 2018
    Sluggy likes this.
  8. Sluggy

    Sluggy

    Joined:
    Nov 27, 2012
    Posts:
    983
    Specifically I was refering to the garbage generated by the single-hit raycast. This was introduced sometime in Unity 5 but has apparently been fixed since then. I recal discovering this at some point and consequently replacing all versions of the single-hit with the non-allocating multi-hit in my code. It's just become such a habit that I never thought to look back.

    Silly me for not realizing that was the 2D physics. I didn't notice '2D' in the function prototypes in the title so I just assumed 3D.
     
    MelvMay likes this.
  9. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,456
    I’m not aware of any old GC-alloc bugs in that function as it returns a struct so very odd but glad you are okay using it now.
     
  10. mkgame

    mkgame

    Joined:
    Feb 24, 2014
    Posts:
    592
    I'am in Unity 2018.3, but the implementation is the worse case. I can live with the order:

    Code (CSharp):
    1. int rayCastRet = Physics.RaycastNonAlloc(ray, results, 500, HitThisLayers);
    2.             if (rayCastRet == 0) {
    3.                 return;
    4.             }
    5.          
    6.             Array.Sort(results, delegate(RaycastHit hit1, RaycastHit hit2) { return hit1.distance.CompareTo(hit2.distance); } );
    But not that the first 'n' hits are not in the array. I see no use in this method. A better way would be to use single raycast, remember the position, and make a new raycast and so on. DO NOT USE THIS "RaycastNonAlloc" and RaycastNon!!! Dear Unity team, I would recommend to deprecate and remove it later, or implement it correctly.
     
  11. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,456
    It would help if you could be clear on exactly what problem you're having.

    You're saying that you're missing hits? If so, have you created a bug report or have a repro case that can be passed along to the 3D physics team? (I'm not in that team btw). Note that if this is the case, it's also off-topic from the thread.
     
    Last edited: Mar 6, 2019
  12. mkgame

    mkgame

    Joined:
    Feb 24, 2014
    Posts:
    592
    For eg. if I set the RaycastHit array to a length of 2, and I have about 8 colliders that can be hit, then in this case the RaycastAll method returns non determined 2 of the 8 collider hits, not the first and the second closest.

    I already implemented a solution, sorted and returns the closests first. It also has a stop layer mask for optimization (eg. stop after hitting the terrain or water layer):

    Code (CSharp):
    1.  
    2.         public static int RaycastAllNonAlloc(Ray ray, RaycastHit[] hits, float range, LayerMask HitLayers, LayerMask StopLayers) {
    3.             int maxHits = hits.Length;
    4.             if (maxHits == 0) {
    5.                 Debug.LogError("This RaycastAll makes no sense with empty hits array.");
    6.                 return 0;
    7.             }
    8.      
    9.             int index = 0;
    10.             while (Physics.Raycast(ray, out hits[index], range, HitLayers)) {
    11.                 if ((1 << hits[index].collider.gameObject.layer & StopLayers.value) > 0) {
    12.                     index++;
    13.                     break;
    14.                 }
    15.          
    16.                 range = range - Vector3.Distance(ray.origin, hits[index].point);
    17.                 ray.origin = hits[index].point + ray.direction * 0.001f;
    18.                 if (range <= 0) {
    19.                     index++;
    20.                     break;
    21.                 }
    22.          
    23.                 index++;
    24.          
    25.                 if (index == maxHits) {
    26.                     break;
    27.                 }
    28.             }
    29.      
    30.             return index;
    31.         }
    For the c++ compiler the loop should be a for loop, else the CPU cannot process the branches in different pipelines.
     
    Last edited: Mar 14, 2019
  13. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,456
    Yes, that's because the results are not sorted in 3D. The idea with the non-alloc stuff is that you provide an array large enough and reuse it. They'll probably add List<T> support at some point same as 2D physics.
     
  14. mkgame

    mkgame

    Joined:
    Feb 24, 2014
    Posts:
    592
    A list based container for RaycastAll woud be better, but as I know, RaycastAll already returns all the RaycastHits. In case of RaycastNonAlloc it must be an array. RaycastNonAlloc has the advantage, that by the array length the max number of RaycastHits is defined and it doesn't allocate memory on the heap additionally. If the RaycastNonAlloc would hit too much colliders, then the limitation of RaycastHits could save the FPS.
     
  15. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,456
    I'm not sure you're following what I meant. The difference is that RaycastAll creates a list for you whereas the lists-based ones I was referring to in 2D physics allow you to pass/reuse your own list as an argument meaning they don't allocate e.g. Physics2D.Raycast.

    Passing an array limits you to the size of the array and you have to create one large enough for all results but you don't know if you exceeded it and results were dropped.
     
  16. mkgame

    mkgame

    Joined:
    Feb 24, 2014
    Posts:
    592
    If you add a new RaycastHit to the list, then the RaycastHit must be created, the default constructor will be called. In case of a pre-instantiated RaycastHit in an array, the RaycastHit must not be instantiated, just values must be assigned, like 'point', 'collider'. I don't know how this has been implemented, but this is the only one way how this can work as non allocating.
     
  17. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,456
    Whilst you didn't ask a question and I'm not sure I exactly follow what you're getting at maybe just saying that all the non-allocating calls all return the number of hits populated in the array as a return value helps. Only those ones will be fully initialised. Any others are undefined and may still be left as they were if previously used.

    In other words, if you pass an array with 1000 elements and the return value is 3 then only those 3 will be fully populated. I know there was a bug previously where the whole array was reset by the script binding system which cause massive CPU spikes if you passed very large arrays but that was fixed (it affect both 2D & 3D because it was a common script binding issue). Now the remaining items in the array beyond the result count are left untouched.
     
  18. mkgame

    mkgame

    Joined:
    Feb 24, 2014
    Posts:
    592
    I agree, the only one thing I wanted to tell is, that c# List will always allocate memory on the heap and that the signature of the RaycastNonAlloc is fine, but not the result. I also agree with RaycastAll, that returns an Array yet, could return in the future a List. I would also recommend to introduce a Stop-Mask, because why not use the fast mask/layer check to limit the raycast checks.
    However, I'am happy with the short funktion I posted here, and I'am pretty sure, that I will not use the built in RaycastAll methods at all, because of the strange results.
     
  19. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,456
    What strange results? It's not clear from what you've posted what exactly is wrong.

    All 2D physics queries in 2019.1 now allow you to pass a List<T> to be populated.

    Physics queries already have this. You specify the layer-mask to select which items you want.
     
  20. mkgame

    mkgame

    Joined:
    Feb 24, 2014
    Posts:
    592
    This post is about sorting order for 3D raycasts. I read the first comments and read the title. My comments belongs to this post. I'm not interested in the 2D raycast version, for that maybe a new post shoud be made.

    The 3D version for RaycastAll and RaycastNonAlloc provides strange results, non sorted and not the first hits, if the buffer is limited in the non alloc raycast.

    A stop mask would simply break and he loop for performance reasons in my implementation, not sure how Unity implementation could use it.
     
  21. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,456
    If you don't provide enough buffer then you'll obviously loose results. The "strange" here is caused by the inadequate buffer size. Obviously the lack of sorting therefore means you could loose results that are potentially closer than the ones it can pass back.

    I only mention the 2D stuff because I work on 2D physics and hinting at the way forward here i.e. 3D being able to allow you to pass a List<T> rather than a fixed array.

    @yant Would know more about reasons/rationale/future.
     
  22. yant

    yant

    Unity Technologies

    Joined:
    Jul 24, 2013
    Posts:
    596
    Right, so in 3D the query hits are not sorted and sent out to scripts exactly as they come out of the physics engine by design. I think this is a consequence of how PhysX computes results. It does a bounding volume overlap where potential hits are discovered in no particular order and then iterates through the hits indeed, classifying them as either blocking or touching, but that doesn't necessarily imply the continuous ray traversal from the origin along the direction that would make sorting a natural consequence of computation order.

    All that said, historically, we didn't sort in the Unity integration code either, for performance reasons.

    Finally, as mentioned above, struggling with RaycastNonAlloc to get it mimic exactly what Raycast would give out of the box is probably not what is encouraged by the current API I'm afraid.
     
    sj631 likes this.
  23. gameDevMode

    gameDevMode

    Joined:
    Jun 18, 2018
    Posts:
    12
    Hi There! I was writing my custom controller and i came out with a solution. I only Need the closest Hit! But i think you can also get the sorted array from this!
    Code (CSharp):
    1.         private int RayCast(Vector3 center, Vector3 castDirection, float maxDistance, out RaycastHit closestHit)
    2.         {
    3.             //Normalize the Cast Direction
    4.             castDirection.Normalize();
    5.  
    6.             int noOfHits = Physics.RaycastNonAlloc(center, castDirection, _rayHits,
    7.                 maxDistance, discludePlayerLayer, QueryTriggerInteraction.Ignore);
    8.  
    9.             //Get the Closest Hit
    10.             float closestDist = Mathf.Infinity;
    11.             int filteredHits = 0;    //The Number of Valid Hits
    12.  
    13.             for (int i = 0; i < noOfHits; i++)
    14.             {
    15.                 if (_rayHits[i].distance < closestDist)
    16.                 {
    17.                     filteredHits++;
    18.                     closestDist = _rayHits[i].distance;
    19.                 }
    20.             }
    21.            
    22.             //Assign the Closest hit!
    23.             closestHit = filteredHits > 0 ? _rayHits[filteredHits - 1] : _rayHits[0];
    24.            
    25.             //Return the Number of Filtered Hits
    26.             return filteredHits;
    27.         }
     
    Xtro likes this.
  24. anton_unity626

    anton_unity626

    Joined:
    Mar 9, 2022
    Posts:
    3
    Sorry for necropost, just want to save some lives.
    Let's imagine you have 4 hits and filtered hits were hit 0 and hit 3, hence filteredHits = 2. So you will return a hit [1], which is further then both passed the filtering ?
    Easy fix is just to have an
    int closestHitIndex = -1;
    and assign it in the loop, then
    closestHit = closestHitIndex >= 0 ? _rayHits[closestHitIndex] : _rayHits[0];


    But In my code I just do assigning of the first (index 0) item before the loop and then compare it to the items in rest of the array starting loop from index 1.