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. Dismiss Notice

How can I choose random points inside a Polygon Collider 2D?

Discussion in 'Scripting' started by JorobusLab, Aug 27, 2014.

  1. JorobusLab

    JorobusLab

    Joined:
    Jul 3, 2014
    Posts:
    78
    Hi guys, can u give me a light about this concern? Let's suppose that I have a tree branch which uniform sides, so I have to pick a polygon collider 2D here. In this branch I want to put some leave sprites but randomly placed. Can you give me some help about how can I generate random points inside this polygin collider so I can place this leaves randomly? Is there any API method to make this easy? Thanks for your time.
     
  2. Frostraver

    Frostraver

    Joined:
    Oct 26, 2012
    Posts:
    25
    Reviving old thread if that is allowed. I would like to know this as well. I'm looking to generate a single random point inside a polygon collider 2d
     
  3. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,194
    Find the polygon's bounding box, and pick a random point inside of that. If the point isn't inside the polygon, try again. Repeat until you have a point.

    If every point inside the bounding box is as likely to be picked, the selection should be distributed uniformly over the polygon.

    This is probably a LOT faster than some fancy algorithm that hits inside the polygon on every attempt.
     
    Hyblademin likes this.
  4. Hyblademin

    Hyblademin

    Joined:
    Oct 14, 2013
    Posts:
    725
    To add, in case it isn't already known: Use Physics2D.OverlapPoint() to check whether the point is in the collider.
     
  5. Frostraver

    Frostraver

    Joined:
    Oct 26, 2012
    Posts:
    25
    @Baste that sounds interesting. I'll try that. The problem with this that I see is that I still need some kind of loop in case the point is outside of the polygon. And, the fact that this stops the game is a massive problem. Do you see a way of solving this?

    @Hyblademin: Yes, I know about that one but I can't use it. I need to generate a point within a certain bounds like Baste suggested. It's not about user input or something similar (which is something I would use the method for that you linked)
     
  6. Hyblademin

    Hyblademin

    Joined:
    Oct 14, 2013
    Posts:
    725
    Frost, Baste's method is not likely to slow the game down by any noticeable degree unless the ratio of the polygon area to the collider's bounding box is very, very small and only if the check is run each frame.

    Also, you will need to use OverlapPoint() to use Baste's method. I was only pointing out which function can be used to check if a randomly chose point actually lies within the collider, in case anyone interested didn't know about it.
     
    coopfly likes this.
  7. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,194
    If you have shapes that fill up a very small part of the bounding box (say you have a tree-shape with very narrow and long branches), you might see a lot of tries. But, even if the polygon only takes up 1/4th of the bounding box, the probability of getting a match reaches 99% after 17 attempts, so I doubt the impact will ever be noticeable, unless PolygonCollider2D's OverlapPoint is incredibly poorly optimized.
     
  8. Frostraver

    Frostraver

    Joined:
    Oct 26, 2012
    Posts:
    25
    Thanks for the numbers. That works reassuring! I'm trying it right now and I'll get back with the results
     
  9. Frostraver

    Frostraver

    Joined:
    Oct 26, 2012
    Posts:
    25
    As I feared my system does seem to hang on this. Here's a picture of the collider I use for testing. The yellow box is it's bounds.



    Code (CSharp):
    1. public Vector3 PointInArea() {
    2.         var bounds = collider.bounds;
    3.         var center = bounds.center;
    4.  
    5.         float x = 0;
    6.         float y = 0;
    7.         do {
    8.             x = UnityEngine.Random.Range(center.x - bounds.extents.x, center.x + bounds.extents.x);
    9.             y = UnityEngine.Random.Range(center.y - bounds.extents.y, center.y + bounds.extents.y);
    10.         } while (Physics2D.OverlapPoint(new Vector2(x, y), LayerMask.NameToLayer("Area")) == null);
    11.  
    12.         return new Vector3(x, y, 0);
    13.     }
    Also, this is the code that I'm using to generate the point and test it. This thing should be done four times as fast as possible at the moment and it just hangs Unity, I can keep waiting but nothing changes. I've looked at my code but I can't find anything that might break this. The code inside the while-loop works for regular boxcollider2d objects so I don't see why it wouldn't work with the probability that you said @Baste
     
  10. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,194
    There's a couple of small optimizations. First of all, check the Collider's OverlapPoint, not the Physics2D. Physics2D.OverlapPoint checks against all colliders, while Collider2D.OverlapPoint checks against that specific collider.

    Additionally, don't do the LayerMask.NameToLayer("Area") lookup every check. Store the result of the check before the do-while (or in Awake/Start).

    Finally, check how many attempts are happening. Looking at your model, it looks like a random point should be inside the shape about half the time, so you should hit on average after ~2 attempts. If your while-loop is running a lot more times than that, something fishy's up.
     
  11. Frostraver

    Frostraver

    Joined:
    Oct 26, 2012
    Posts:
    25
    I switched to the Collider2D.OverlapPoint and now it seems to be working just fine, no framedrops noticeable.

    Code (CSharp):
    1.  
    2. public Vector3 PointInArea() {
    3.         var bounds = collider.bounds;
    4.         var center = bounds.center;
    5.  
    6.         float x = 0;
    7.         float y = 0;
    8.         int attempt = 0;
    9.         do {
    10.             x = UnityEngine.Random.Range(center.x - bounds.extents.x, center.x + bounds.extents.x);
    11.             y = UnityEngine.Random.Range(center.y - bounds.extents.y, center.y + bounds.extents.y);
    12.             attempt++;
    13.         } while (!collider.OverlapPoint(new Vector2(x, y)) || attempt <= 100);
    14.         Debug.Log("Attemps: " + attempt);
    15.  
    16.         return new Vector3(x, y, 0);
    17.     }
    18.  
    This is what the code is now. Weird thing is that the attempt value is always above 100 (101, 102, 106,.. I haven't seen it going above 106 in my last couple tries) which is something you should notice but I don't... I might just be becoming extremely stupid.

    Thanks for the help! Much appreciated!
     
  12. Zaflis

    Zaflis

    Joined:
    May 26, 2014
    Posts:
    438
    Try changing || to && in the while condition. Using logical or there makes the result of the overlappoint meaningless, as either condition will make it go on another round.

    You could also use bounds.min.x to bounds.max.x and so on for random range. Makes that code a little bit shorter.
     
    Joy-less and ThermalFusion like this.
  13. d2clon

    d2clon

    Joined:
    Aug 19, 2017
    Posts:
    19
    My version:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class RandomPointInCollider
    4. {
    5.     Collider2D collider;
    6.     Vector3 minBound;
    7.     Vector3 maxBound;
    8.  
    9.     public RandomPointInCollider(Collider2D collider)
    10.     {
    11.       this.collider = collider;
    12.       this.minBound = collider.bounds.min;
    13.       this.maxBound = collider.bounds.max;
    14.     }
    15.  
    16.     public Vector3 RandomPoint()
    17.     {
    18.       Vector3 randomPoint;
    19.  
    20.       do {
    21.         randomPoint =
    22.           new Vector3(
    23.             Random.Range(minBound.x, maxBound.x),
    24.             Random.Range(minBound.y, maxBound.y),
    25.             Random.Range(minBound.z, maxBound.z)
    26.           );
    27.       } while(!collider.OverlapPoint(randomPoint));
    28.  
    29.       return randomPoint;
    30.     }
    31. }

    https://gist.github.com/fguillen/dc53b2fbc74c744a4f4cdd6976a2ff36