Space Filling with Circles of Unequal Size

Space filling with circles of unequal size

I would try to insert sphere after sphere (largest first). Each one is added in the largest available space, with some random jitter.

One relatively easy way to find (more or less) the largest available space, is to imagine a grid of points on your view and store for each grid point (in a 2D array) the closest distance to any item: edge or sphere, whichever is closest. This array is updated as each new sphere is added.

To add a new sphere, just take the grid point with highest distance and apply some random jitter (you actually know how much you can jitter, because you know the distance to the closest item). (I would randomize not more than (d-r)/2 where d is the distance in the array and r is the radius of the sphere to add.

Updating this array after adding another circle is no rocket science: you calculate for each grid point the distance to newly added sphere and replace the stored value if that was larger.

It is possible that your grid is too coarse, and you can't add any more circle (when the 2D array contains no distances larger than the radius of the circle to add). Then you have to increase (e.g. double) the grid resolution before continuing.

Here are some result of this implementation (it took me about 100 lines of code)

  • 100 Circles of varying size

100 circles of varying size

  • 500 Circles of varying size

500 circles of varying size

  • 100 Circles of same size

Sample Image

And here is some rough C++ code (just the algorithm, don't expect this to compile)

    // INITIALIZATION

// Dimension of canvas
float width = 768;
float height = 1004;

// The algorithm creates a grid on the canvas
float gridSize=10;

int gridColumns, gridRows;
float *dist;

void initDistances()
{
// Determine grid dimensions and allocate array
gridColumns = width/gridSize;
gridRows = height/gridSize;

// We store a 2D array as a 1D array:
dist = new float[ gridColumns * gridRows ];

// Init dist array with shortest distances to the edges
float y = gridSize/2.0;
for (int row=0; row<gridRows; row++)
{
float distanceFromTop = y;
float distanceFromBottom = height-y;
for (int col=0; col<gridColumns; col++)
{
int i = row*gridColumns+col;
dist[i]=(distanceFromTop<distanceFromBottom?distanceFromTop:distanceFromBottom);
}
y+=gridSize;
}
float x = gridSize/2.0;
for (int col=0; col<gridColumns; col++)
{
float distanceFromLeft = x;
float distanceFromRight = width-x;
for (int row=0; row<gridRows; row++)
{
int i = row*gridColumns+col;
if (dist[i]>distanceFromLeft) dist[i] = distanceFromLeft;
if (dist[i]>distanceFromRight) dist[i] = distanceFromRight;
}
x+=gridSize;
}
}

void drawCircles()
{
for (int circle = 0; circle<getNrOfCircles(); circle++)
{
// We assume circles are sorted large to small!
float radius = getRadiusOfCircle( circle );

// Find gridpoint with largest distance from anything
int i=0;
int maxR = 0;
int maxC = 0;
float maxDist = dist[0];

for (int r=0; r<gridRows; r++)
for (int c=0; c<gridColumns; c++)
{
if (maxDist<dist[i]) {
maxR= r; maxC= c; maxDist = dist[i];
}
i++;
}

// Calculate position of grid point
float x = gridSize/2.0 + maxC*gridSize;
float y = gridSize/2.0 + maxR*gridSize;

// Apply some random Jitter
float offset = (maxDist-radius)/2.0;
x += (rand()/(float)RAND_MAX - 0.5) * 2 * offset;
y += (rand()/(float)RAND_MAX - 0.5) * 2 * offset;

drawCircle(x,y,radius);

// Update Distance array with new circle;
i=0;
float yy = gridSize/2.0;
for (int r=0; r<gridRows; r++)
{
float xx = gridSize/2.0;
for (int c=0; c<gridColumns; c++)
{
float d2 = (xx-x)*(xx-x)+(yy-y)*(yy-y);

// Naive implementation
// float d = sqrt(d2) - radius;
// if (dist[i]>d) dist[i] = d;

// Optimized implementation (no unnecessary sqrt)
float prev2 = dist[i]+radius;
prev2 *= prev2;
if (prev2 > d2)
{
float d = sqrt(d2) - radius;
if (dist[i]>d) dist[i] = d;
}

xx += gridSize;
i++;
}
yy += gridSize;
}
}
}

Filling a rectangle with unequal sized circles with genetic algorithm

Your problem is related to the Knapsack problem: Out of a set of N items with weights W and values V you want to select that group of items that have maximal value, but the sum of their weights remains lower than some bound.

Your problem however is more complex, since the evaluation of the weight-constraint is not a simple addition, but depends on the arrangement of the circles. I think that this constitutes another NP-hard problem to solve. You will have to find some quick approximation on that constraint that tell you if it's possible (and which sometimes may tell you it's not possible, even though it would be).

The arrangement of objects inside a container can be described as a packing problem. You may want to look at circle packing and related literature. A simple relaxation could also be based on rectangles. There are quick methods for rectangle packing which you can use if you treat your circles as rectangles. If your circles are of highly different size, however this may be a bad relaxation.

Algorithm for packing circles within a circle?

There is no general solution, but for problems with up to N=2000 sub circles, the best known packings obtained from numerical methods can be found here. ASCII files of the packing values can be freely downloaded for application use. This is an ongoing search, so checking back for updates might be something you'd want to encode.

Note also there is a little form hiding at the bottom of the page which applies the data to the problem of minimising waste for a given size of circle and sub-circle.

Randomly and efficiently filling space with shapes

Issue you are running into

You are running into the Coupon collector's problem because you are using a technique of Rejection sampling.

You are also making strong assumptions about what a "random filling" is. Your algorithm will leave large gaps between circles; is this what you mean by "random"? Nevertheless it is a perfectly valid definition, and I approve of it.


Solution

To adapt your current "random filling" to avoid the rejection sampling coupon-collector's issue, merely divide the space you are filling into a grid. For example if your circles are of radius 1, divide the larger circle into a grid of 1/sqrt(2)-width blocks. When it becomes "impossible" to fill a gridbox, ignore that gridbox when you pick new points. Problem solved!


Possible dangers

You have to be careful how you code this however! Possible dangers:

  • If you do something like if (random point in invalid grid){ generateAnotherPoint() } then you ignore the benefit / core idea of this optimization.
  • If you do something like pickARandomValidGridbox() then you will slightly reduce the probability of making circles near the edge of the larger circle (though this may be fine if you're doing this for a graphics art project and not for a scientific or mathematical project); however if you make the grid size 1/sqrt(2) times the radius of the circle, you will not run into this problem because it will be impossible to draw blocks at the edge of the large circle, and thus you can ignore all gridboxes at the edge.

Implementation

Thus the generalization of your method to avoid the coupon-collector's problem is as follows:

Inputs: large circle coordinates/radius(R), small circle radius(r)
Output: set of coordinates of all the small circles
Algorithm:
divide your LargeCircle into a grid of r/sqrt(2)

ValidBoxes = {set of all gridboxes that lie entirely within LargeCircle}

SmallCircles = {empty set}

until ValidBoxes is empty:
pick a random gridbox Box from ValidBoxes
pick a random point inside Box to be center of small circle C

check neighboring gridboxes for other circles which may overlap*
if there is no overlap:
add C to SmallCircles
remove the box from ValidBoxes # possible because grid is small
else if there is an overlap:
increase the Box.failcount
if Box.failcount > MAX_PERGRIDBOX_FAIL_COUNT:
remove the box from ValidBoxes

return SmallCircles

(*) This step is also an important optimization, which I can only assume you do not already have. Without it, your doesThisCircleOverlapAnother(...) function is incredibly inefficient at O(N) per query, which will make filling in circles nearly impossible for large ratios R>>r.

This is the exact generalization of your algorithm to avoid the slowness, while still retaining the elegant randomness of it.


Generalization to larger irregular features

edit: Since you've commented that this is for a game and you are interested in irregular shapes, you can generalize this as follows. For any small irregular shape, enclose it in a circle that represent how far you want it to be from things. Your grid can be the size of the smallest terrain feature. Larger features can encompass 1x2 or 2x2 or 3x2 or 3x3 etc. contiguous blocks. Note that many games with features that span large distances (mountains) and small distances (torches) often require grids which are recursively split (i.e. some blocks are split into further 2x2 or 2x2x2 subblocks), generating a tree structure. This structure with extensive bookkeeping will allow you to randomly place the contiguous blocks, however it requires a lot of coding. What you can do however is use the circle-grid algorithm to place the larger features first (when there's lot of space to work with on the map and you can just check adjacent gridboxes for a collection without running into the coupon-collector's problem), then place the smaller features. If you can place your features in this order, this requires almost no extra coding besides checking neighboring gridboxes for collisions when you place a 1x2/3x3/etc. group.

Position different size circles around a circular path with no gaps

It's something like this, but you're gonna have to figure out the last circle's size:

http://jsfiddle.net/rudiedirkx/ufvf62yf/2/

The main logic:

var firstStep = 0, rad = 0, step = 0;
firstStep = step = stepSize();
for ( var i=0; i<30; i++ ) {
draw.radCircle(rad, step);
rad += step;
step = stepSize();
rad += step;
}
  • stepSize() creates a random rad between Math.PI/48 and Math.PI/48 + Math.PI/36 (no special reason, but it looked good). You can fix that to be the right sizes.
  • draw.radCircle(rad, step) creates a circle at position rad of size step (also in rad).
  • step is added twice per iteration: once to step from current circle's center to its edge and once to find the next circle's center
  • firstStep is important because you have to know where to stop drawing (because the first circle crosses into negative angle)
  • I haven't figured out how to make the last circle the perfect size yet =)

There's also a bit of magic in draw.radCircle():

var r = rRad * Math.PI/3 * 200 * .95;
  • The 200 is obviously the big circle's radius
  • The 95% is because the circle's edge length is slightly longer than the (straight) radius of every circle
  • I have no idea why Math.PI/3 is that... I figured it had to be Math.PI/2, which is 1 rad, but that didn't work at all. 1/3 for some reason does..... Explain that!

If you want to animate these circle sizes and keep them aligned, you'll have a hard time. They're all random now. In an animation, only the very first iteration can be random, and the rest is a big mess of cache and math =)

Position N circles of different radii inside a larger circle without overlapping

I have a pretty naive one pass (over the radii) solution that produces alright results, although there is definitely room for improvement. I do have some ideas in that direction but figure I might as well share what I have in case anybody else wants to hack on it too.

alt text

It looks like they intersect at the center, but they don't. I decorated the placement function with a nested loop that checks every circle against every other circle (twice) and raises an AssertionError if there is an intersection.

Also, I can get the edge close to perfect by simply reverse sorting the list but I don't think the center looks good that way. It's (pretty much the only thing ;) discussed in the comments to the code.

The idea is to only look at discrete points that a circle might live at and iterate over them using the following generator:

def base_points(radial_res, angular_res):
circle_angle = 2 * math.pi
r = 0
while 1:
theta = 0
while theta <= circle_angle:
yield (r * math.cos(theta), r * math.sin(theta))
r_ = math.sqrt(r) if r > 1 else 1
theta += angular_res/r_
r += radial_res

This just starts at the origin and traces out points along concentric circles around it. We process the radii by sorting them according to some parameters to keep the large circles near the center (beginning of list) but enough small ones near the beginning to fill in spaces. We then iterate over the radii. within the main loop, we first loop over points that we have already looked at and saved away. If none of those are suitable, we start pulling new points out of the generator and saving them (in order) until we find a suitable spot. We then place the circle and go through our list of saved points pulling out all of the ones that fall within the new circle. We then repeat. on the next radius.

I'll put some ideas I have into play and make it mo`bettah. This might serve as a good first step for a physics based idea because you get to start with no overlaps. Of course it might already be tight enough so that you wouldn't have much room.

Also, I've never played with numpy or matplotlib so I write just vanilla python. There might be something in there that will make it run much faster, I'll have to look.



Related Topics



Leave a reply



Submit