views:

198

answers:

6

I'm using procedural techniques to generate graphics for a game I am writing.

To generate some woods I would like to scatter trees randomly within a regular hexagonal area centred at <0,0>.

What is the best way to generate these points in a uniform way?

+12  A: 

If you can find a good rectangular bounding box for your hexagon, the easiest way to generate uniformly random points is by rejection sampling (http://en.wikipedia.org/wiki/Rejection_sampling)

That is, find a rectangle that entirely contains your hexagon, and then generate uniformly random points within the rectangle (this is easy, just independently generate random values for each coordinate in the right range). Check if the random point falls within the hexagon. If yes, keep it. If no, draw another point.

So long as you can find a good bounding box (the area of the rectangle should not be more than a constant factor larger than the area of the hexagon it encloses), this will be extremely fast.

Aaron
Chances of failure with this method is 25%, which is pretty high I would think. We can pick the points deterministically with three random number calls (see my answer), but on second thought, this answer's expected number of random number calls will likely beat that answer's number of random number calls...
Moron
I did some calculations: Expected number of times you have to pick a random point = 4/3. Picking a random point = 2 random number calls. Thus the expected number of random number calls = 8/3 = 2.6667, which beats 3 anytime :-) +1.
Moron
btw, regarding the comment of constant factor area: If you choose a rectangle so that the chances of failure become more than 1/3, then you should go with the other method which guarantees 3 random number calls per point.
Moron
@Moron: Why 4/3?
Jacob
@Jacob: If you consider the smallest rectangle bounding the hexagon, the area of hexagon is 3/4 the area of rectangle. So this is now same as the problem of finding the expected number of tosses required to get a heads, given heads probability = p (p = 3/4). The answer for that is 1/p, in this case it is 4/3.
Moron
@Moron: Got it, thanks!
Jacob
If you use a circle you would have a p closer to 4/5, but the expense of creating generating the point would probably outweigh the cost of the ~5% more lookups needed for the rectangle. For a uniform distribution in a circle you use polar coords and pick an angle in [0,2pi) and a radius = R*sqrt(rand())
Dolphin
@Dolphin: Quite right! +1 to your comment, again :), but do we need the sqrt?
Moron
Nice approach - I'll may well go with something like this. An idea building on this - how about generating a parallelogram.... I think this means I will only have to chop off two corners (1/8th of the area) to make the hexagon, and the random points in the parallelogram should be easy to generate by multiplying random numbers by two vectors 120 degrees apart
mikera
@mikera: The chopping of two corners gives rise to 3/4 chance of sucess again, just like the above rectagle approach. Your approach might make it easier to do the point in hexagon test, though.
Moron
@Moron of course you're right! My mistake.
mikera
@M: (in response to *"Do we need the sqrt?"*) Yes, or it will not be uniform.
BlueRaja - Danny Pflughoeft
@BlueRaja: Doh! Of course.
Moron
@mikera Doing the parallelogram method would probably work, and rejection becomes as simple as checking to see if the sum of the two random values is between .5 and 1.5 (since the sides of the parallelogram are twice the length of a side of the hex). Pretty cunning!
dash-tom-bang
+2  A: 

The traditional approach (applicable to regions of any polygonal shape) is to perform trapezoidal decomposition of your original hexagon. Once that is done, you can select your random points through the following two-step process:

1) Select a random trapezoid from the decomposition. Each trapezoid is selected with probability proportional to its area.

2) Select a random point uniformly in the trapezoid chosen on step 1.

You can use triangulation instead of trapezoidal decomposition, if you prefer to do so.

AndreyT
A: 

1) make biection from points to numbers (just enumerate them), get random number -> get point.

Another solution.

2) if N - length of hexagon's side, get 3 random numbers from [1..N], start from some corner and move 3 times with this numbers for 3 directions.

Max
#2 is not uniform.
Amadan
+5  A: 

A possibly simple way is the following:

    F ____ B
     /\  /\
  A /__\/__\ E
    \  /\  /
     \/__\/
     D     C

Consider the parallelograms ADCO (center is O) and AOBF.

Any point in this can be written as a linear combination of two vectors AO and AF.

An point P in those two parallelograms satisfies

P = x* AO + y * AF or x*AO + y*AD.

where 0 <= x < 1 and 0 <= y <= 1 (we discount the edges shared with BECO).

Similarly any point Q in the parallelogram BECO can be written as the linear combination of vectors BO and BE such that

Q = x*BO + y*BE where 0 <=x <=1 and 0 <=y <= 1.

Thus to select a random point

we select

A with probability 2/3 and B with probability 1/3.

If you selected A, select x in [0,1) (note, half-open interval [0,1)) and y in [-1,1] and choose point P = x*AO+y*AF if y > 0 else choose P = x*AO + |y|*AD.

If you selected B, select x in [0,1] and y in [0,1] and choose point Q = x*BO + y*BE.

So it will take three random number calls to select one point, which might be good enough, depending on your situation.

Moron
It is not good enough, because it is not uniform. Center is much more likely than edges. He specifically says he wants a uniform distribution.
Amadan
@Amadan: Center is as likely as any other point: 0 probability :-) Is there some non-zero area region that has more chance than some other equal area region?
Moron
@Amadan: In any case, that can be corrected. I have edited the answer.
Moron
I don't think this is quite right, how do you get a point in DOC? If you select A and have y<0 you are outside the hex. I think it simplifies things to just say you first select AOFB, BOEC or CODA, then generate an x and y in [0,1).
Dolphin
@Dolphin: Quite right! That is easily corrected though. Edited. The problem was assuming AD = -AF! :-). Hope it's right now. I agree with the simplify things part too. +1 to your comment.
Moron
I like this solution - there is something very elegant about the areas fitting together perfectly!
mikera
My apologies for rushing forward with basically the same solution without carefully reading yours.
Greg Kuperberg
@Greg: No worries. Always good to know that a math professor agrees with me :-)
Moron
+2  A: 

Chop it up into six triangles (hence this applies to any regular polygon), randomly choose one triangle, and randomly choose a point in the selected triangle.

Choosing random points in a triangle is a well-documented problem.

And of course, this is quite fast and you'll only have to generate 3 random numbers per point --- no rejection, etc.

Update:

Since you will have to generate two random numbers, this is how you do it:

R = random(); //Generate a random number called R between 0-1

S = random(); //Generate a random number called S between 0-1

if(R + S >=1)
{
R = 1 – R;
S = 1 – S;
}
Jacob
However, most of those give the solution which requires generating two random numbers, then discarding them until their sum is under 1. Thus, you might as well just sample a box and discard if outside hex bounds...
Amadan
I've updated my answer to fix this.
Jacob
+1. This seems simpler than my answer :-), but it has got the same issues as the internal 'edge' points being 'more likely' than the external edge points (the concern Amadan had regarding my answer). The issue would not make sense if we were dealing with pure continuous reals (as chances of any point = 0 in that case), but is a reasonable concern practically speaking, since we only work with a finite number of rational approximations.
Moron
+5  A: 

If it's a regular hexagon, the simplest method that comes to mind is to divide it into three rhombuses. That way (a) they have the same area, and (b) you can pick a random point in any one rhombus with two random variables from 0 to 1. Here is a Python code that works.

from math import sqrt
from random import randrange, random
from matplotlib import pyplot

vectors = [(-1.,0),(.5,sqrt(3.)/2.),(.5,-sqrt(3.)/2.)]

def randinunithex():
    x = randrange(3);
    (v1,v2) = (vectors[x], vectors[(x+1)%3])
    (x,y) = (random(),random())
    return (x*v1[0]+y*v2[0],x*v1[1]+y*v2[1])

for n in xrange(500):
    v = randinunithex()
    pyplot.plot([v[0]],[v[1]],'ro')

pyplot.show()

A couple of people in the discussion raised the question of uniformly sampling a discrete version of the hexagon. The most natural discretization is with a triangular lattice, and there is a version of the above solution that still works. You can trim the rhombuses a little bit so that they each contain the same number of points. They only miss the origin, which has to be allowed separately as a special case. Here is a code for that:

from math import sqrt
from random import randrange, random
from matplotlib import pyplot

size = 10

vectors = [(-1.,0),(.5,sqrt(3.)/2.),(.5,-sqrt(3.)/2.)]

def randinunithex():
    if not randrange(3*size*size+1): return (0,0)
    t = randrange(3);
    (v1,v2) = (vectors[t], vectors[(t+1)%3])
    (x,y) = (randrange(0,size),randrange(1,size))
    return (x*v1[0]+y*v2[0],x*v1[1]+y*v2[1])

# Plot 500 random points in the hexagon
for n in xrange(500):
    v = randinunithex()
    pyplot.plot([v[0]],[v[1]],'ro')

# Show the trimmed rhombuses
for t in xrange(3):
    (v1,v2) = (vectors[t], vectors[(t+1)%3])
    corners = [(0,1),(0,size-1),(size-1,size-1),(size-1,1),(0,1)]
    corners = [(x*v1[0]+y*v2[0],x*v1[1]+y*v2[1]) for (x,y) in corners]
    pyplot.plot([x for (x,y) in corners],[y for (x,y) in corners],'b')

pyplot.show()

And here is a picture.

alt text

Greg Kuperberg
Since we will be dealing with a finite number of rationals (and so a finite number of points in hexagon), this does not give a uniform distribution of those points, as the chances of getting origin are greater than chances of getting a point on the edge of the hexagon. (Same issue which Amadan had with my answer). But that can be easily corrected I suppose.
Moron
This solution (and yours, in principle) is a valid floating point implementation of the continuous distribution in which the probability of getting any specific point is 0. The only deviation has to do with roundoff error and limited precision of the random number source. A discrete solution is also a good question, but it's not the same question as the one posed. The author would have to specify how he wants to discretize the hexagon.
Greg Kuperberg
I suppose the OP will not care about the exact discretization and will be happy with whatever reasonable one you can provide (which you are doing already). I agree with you, btw.
Moron
Nice solution - Yeah, OP does not care about the exact boundary conditions - just that the resulting forest looks suitably "uniform" and fits a regular hexagon :-)
mikera
Actually my solution is equivalent to Moron's, I just didn't read carefully. On the positive side, I coded it.
Greg Kuperberg
Yeah, +1 for that :-)
Moron
+1 for the use of a cool lib (amazing what python lib are out there)
Quonux