10

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?

mikera
  • 105,238
  • 25
  • 256
  • 415
  • 2
    Is it guaranteed to be a *regular* hexagon? If so, why isn't it stated in the question? If not, what does "centered" mean then? – AnT stands with Russia Jul 13 '10 at 18:51
  • Yes, it is a regular Hexagon - good spot, edited to reflect. – mikera Jul 13 '10 at 19:45
  • Thanks for all the very interesting solutions: One additional clarification, it's not absolutely necessary to minimise the number of random number calls though of course it helps – mikera Jul 13 '10 at 19:50
  • @mikera: Well, it doesn't seem to matter since simplest solution here minimizes the average number of calls to a `random`! – Jacob Jul 13 '10 at 21:04
  • 1
    Thanks everyone for all the great ideas - here is an image showing the finished result: http://yfrog.com/0d100713ironcladearlymap2p – mikera Jul 13 '10 at 22:40
  • p.s. I ended up using a hybrid approach that was closest to Greg/Moron's approach because they nicely exploit the hexagonal shape, though I also think that Aarons answer is probably the best "general" solution to creating a uniform covering for complex shapes. – mikera Jul 13 '10 at 22:44

8 Answers8

13

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
  • 692
  • 3
  • 8
  • 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... –  Jul 13 '10 at 17:46
  • 1
    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. –  Jul 13 '10 at 17:55
  • 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. –  Jul 13 '10 at 18:03
  • @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. –  Jul 13 '10 at 18:31
  • 3
    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 Jul 13 '10 at 19:22
  • @Dolphin: Quite right! +1 to your comment, again :), but do we need the sqrt? –  Jul 13 '10 at 19:31
  • 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 Jul 13 '10 at 19:54
  • @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. –  Jul 13 '10 at 19:59
  • 1
    @M: (in response to *"Do we need the sqrt?"*) Yes, or it will not be uniform. – BlueRaja - Danny Pflughoeft Jul 13 '10 at 20:03
  • @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 Jul 16 '10 at 19:32
8

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 xAO + yAD.

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 = xBO + yBE 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 = xAO+yAF 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 = xBO + yBE.

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

  • 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 Jul 13 '10 at 18:08
  • 1
    @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? –  Jul 13 '10 at 18:11
  • @Amadan: In any case, that can be corrected. I have edited the answer. –  Jul 13 '10 at 18:24
  • 1
    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 Jul 13 '10 at 19:04
  • @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. –  Jul 13 '10 at 19:09
  • I like this solution - there is something very elegant about the areas fitting together perfectly! – mikera Jul 13 '10 at 19:49
  • My apologies for rushing forward with basically the same solution without carefully reading yours. – Greg Kuperberg Jul 13 '10 at 22:43
  • @Greg: No worries. Always good to know that a math professor agrees with me :-) –  Jul 13 '10 at 22:46
7

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 http://www.freeimagehosting.net/uploads/0f80ad5d9a.png

Greg Kuperberg
  • 3,995
  • 22
  • 24
  • 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. –  Jul 13 '10 at 21:58
  • 2
    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 Jul 13 '10 at 22:03
  • 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. –  Jul 13 '10 at 22:08
  • 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 Jul 13 '10 at 22:23
  • 1
    Actually my solution is equivalent to Moron's, I just didn't read carefully. On the positive side, I coded it. – Greg Kuperberg Jul 13 '10 at 22:40
  • +1 for the use of a cool lib (amazing what python lib are out there) – Quonux Jul 16 '10 at 21:48
  • I'm a little late to the party but can you please explain the purpose of size and the line `if not randrange(3*size*size+1): return (0,0)` ? – crypto Jun 27 '18 at 22:25
  • The origin is one of the points in the hexagon and the rest of the algorithm only choses randomly from the other points. – Greg Kuperberg Jun 28 '18 at 17:27
  • @GregKuperberg So the first segment of code generates random points within a hexagon while the second one is sampling from a set of lattice points? I was interested in the former for my problem of generating random terminal coordinates within a hexagonal cell coverage area. Thanks! – crypto Jun 28 '18 at 18:23
  • 1
    The first block of code is a function to generate one random lattice point in the hexagon. The second block calls that function 500 times and plots the results. The third block of code shows the guide rhombuses used in the function. – Greg Kuperberg Jun 29 '18 at 19:28
  • Link to image is dead. – TheForgot3n1 Sep 10 '22 at 12:50
2

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.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
1

You may check my 2009 paper, where I derived an "exact" approach to generate "random points" inside different lattice shapes: "hexagonal", "rhombus", and "triangular". As far as I know it is the "most optimized approach" because for every 2D position you only need two random samples. Other works derived earlier require 3 samples for each 2D position!

Hope this answers the question!

http://arxiv.org/abs/1306.0162

1

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;
}
Community
  • 1
  • 1
Jacob
  • 34,255
  • 14
  • 110
  • 165
  • 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 Jul 13 '10 at 18:13
  • +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. –  Jul 13 '10 at 18:54
0

The rejection sampling solution above is intuitive and simple, but uses a rectangle, and (presumably) euclidean, X/Y coordinates. You could make this slightly more efficient (though still suboptimal) by using a circle with radius r, and generate random points using polar coordinates from the center instead, where distance would be rand()*r, and theta (in radians) would be rand()*2*PI.

roach374
  • 61
  • 1
  • 6
  • Actually, come to think of it, this wouldn't be very uniform, since trees closer to the center of the circle would be closer to each other. That is, trees on two theta values, t1 and t2 would be physically closer together near the "pole" than they would at the edge of the circle. – roach374 Mar 04 '13 at 05:00
0

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
  • 4,792
  • 4
  • 29
  • 32