views:

258

answers:

7

I'm drawing rectangles at random positions on the stage, and I don't want them to overlap. So for each rectangle, I need to find a blank area to place it.

I've thought about trying a random position, verify if it is free with

private function containsRect(r:Rectangle):Boolean {
    var free:Boolean = true;
    for (var i:int = 0; i < numChildren; i++)
        free &&= getChildAt(i).getBounds(this).containsRect(r);
    return free;
}

and in case it returns false, to try with another random position.

The problem is that if there is no free space, I'll be stuck trying random positions forever.

There is an elegant solution to this?

+1  A: 

I'm not sure how elegant this would be, but you could set up a maximum number of attempts. Maybe 100?

Sure you might still have some space available, but you could trigger the "finish" event anyway. It would be like when tween libraries snap an object to the destination point just because it's "close enough".

HTH

Zárate
+1  A: 

One possible check you could make to determine if there was enough space, would be to check how much area the current set of rectangels are taking up. If the amount of area left over is less than the area of the new rectangle then you can immediately give up and bail out. I don't know what information you have available to you, or whether the rectangles are being laid down in a regular pattern but if so you may be able to vary the check to see if there is obviously not enough space available.

This may not be the most appropriate method for you, but it was the first thing that popped into my head!

chillysapien
A: 

Assuming you define the dimensions of the rectangle before trying to draw it, I think something like this might work:

Establish a grid of possible centre points across the stage for the candidate rectangle. So for a 6x4 rectangle your first point would be at (3, 2), then (3 + 6 * x, 2 + 4 * y). If you can draw a rectangle between the four adjacent points then a possible space exists.

for (x = 0, x < stage.size / rect.width - 1, x++)
    for (y = 0, y < stage.size / rect.height - 1, y++)
        if can_draw_rectangle_at([x,y], [x+rect.width, y+rect.height])
            return true;

This doesn't tell you where you can draw it (although it should be possible to build a list of the possible drawing areas), just that you can.

Mike Woodhouse
+3  A: 

You can simplify things with a transformation. If you're looking for a valid place to put your LxH rectangle, you can instead grow all of the previous rectangles L units to the right, and H units down, and then search for a single point that doesn't intersect any of those. This point will be the lower-right corner of a valid place to put your new rectangle.

Next apply a scan-line sweep algorithm to find areas not covered by any rectangle. If you want a uniform distribution, you should choose a random y-coordinate (assuming you sweep down) weighted by free area distribution. Then choose a random x-coordinate uniformly from the open segments in the scan line you've selected.

I don't have Karma to comment on your selected answer, but the deterministic version you have selected as the right answer does not work since it doesn't allow you to place rectangles that span multiple regions. You need to apply the transformation I mention above first.
And the trick I mentioned depends on the size of the new rectangle you're adding, so it's not like you can maintain a hierarchical data structure that already incorporates this. His answer is broken. Reread mine.
A: 

I think that the only efficient way to do this with what you have is to maintain a 2D boolean array of open locations. Have the array of sufficient size such that the drawing positions still appear random.

When you draw a new rectangle, zero out the corresponding rectangular piece of the array. Then checking for a free area is constant^H^H^H^H^H^H^H time. Oops, that means a lookup is O(nm) time, where n is the length, m is the width. There must be a range based solution, argh.

Edit2: Apparently the answer is here but in my opinion this might be a bit much to implement on Actionscript, especially if you are not keen on the geometry.

Overflown
+3  A: 

Let S be the area of the stage. Let A be the area of the smallest rectangle we want to draw. Let N = S/A

One possible deterministic approach:

When you draw a rectangle on an empty stage, this divides the stage into at most 4 regions that can fit your next rectangle. When you draw your next rectangle, one or two regions are split into at most 4 sub-regions (each) that can fit a rectangle, etc. You will never create more than N regions, where S is the area of your stage, and A is the area of your smallest rectangle. Keep a list of regions (unsorted is fine), each represented by its four corner points, and each labeled with its area, and use weighted-by-area reservoir sampling with a reservoir size of 1 to select a region with probability proportional to its area in at most one pass through the list. Then place a rectangle at a random location in that region. (Select a random point from bottom left portion of the region that allows you to draw a rectangle with that point as its bottom left corner without hitting the top or right wall.)

If you are not starting from a blank stage then just build your list of available regions in O(N) (by re-drawing all the existing rectangles on a blank stage in any order, for example) before searching for your first point to draw a new rectangle.

Note: You can change your reservoir size to k to select the next k rectangles all in one step.

Note 2: You could alternatively store available regions in a tree with each edge weight equaling the sum of areas of the regions in the sub-tree over the area of the stage. Then to select a region in O(logN) we recursively select the root with probability area of root region / S, or each subtree with probability edge weight / S. Updating weights when re-balancing the tree will be annoying, though.

    Runtime: O(N)
    Space: O(N)

One possible randomized approach:

Select a point at random on the stage. If you can draw one or more rectangles that contain the point (not just one that has the point as its bottom left corner), then return a randomly positioned rectangle that contains the point. It is possible to position the rectangle without bias with some subtleties, but I will leave this to you.

At worst there is one space exactly big enough for our rectangle and the rest of the stage is filled. So this approach succeeds with probability > 1/N, or fails with probability < 1-1/N. Repeat N times. We now fail with probability < (1-1/N)^N < 1/e. By fail we mean that there is a space for our rectangle, but we did not find it. By succeed we mean we found a space if one existed. To achieve a reasonable probability of success we repeat either Nlog(N) times for 1/N probability of failure, or N² times for 1/e^N probability of failure.

Summary: Try random points until we find a space, stopping after NlogN (or N²) tries, in which case we can be confident that no space exists.

    Runtime: O(NlogN) for high probability of success, O(N²) for very high probability of success
    Space: O(1)
Imran
A: 

Here's the algorithm I'd use

  1. Put down N number of random points, where N is the number of rectangles you want
  2. iteratively increase the dimensions of rectangles created at each point N until they touch another rectangle.

You can constrain the way that the initial points are put down if you want to have a minimum allowable rectangle size.

If you want all the space covered with rectangles, you can then incrementally add random points to the remaining "free" space until there is no area left uncovered.

Ryan