4

Related: Is there a simple algorithm for calculating the maximum inscribed circle into a convex polygon?

I'm writing a graphics program whose goals are artistic rather than mathematical. It composes a picture step by step, using geometric primitives such as line segments or arcs of small angle. As it goes, it looks for open areas to fill in with more detail; as the available open areas get smaller, the detail gets finer, so it's loosely fractal.

At a given step, in order to decide what to do next, we want to find out: where is the largest circular area that's still free of existing geometric primitives?

Some constraints of the problem

  • It does not need to be exact. A close-enough answer is fine.
  • Imprecision should err on the conservative side: an almost-maximal circle is acceptable, but a circle that's not quite empty isn't acceptable.
  • CPU efficiency is a priority, because it will be called often.
  • The program will run in a browser, so memory efficiency is a priority too.
  • I'll have to set a limit on level of detail, constrained presumably by memory space.
  • We can keep track of the primitives already drawn in any way desired, e.g. a spatial index. Exactness of these is not required; e.g. storing bounding boxes instead of arcs would be OK. However the more precision we have, the better, because it will allow the program to draw to a higher level of detail. But, given that the number of primitives can increase exponentially with the level of detail, we'd like storage of past detail not to increase linearly with the number of primitives.

To summarize the order of priorities

  1. Memory efficiency
  2. CPU efficiency
  3. Precision

P.S.

I framed this question in terms of circles, but if it's easier to find the largest clear golden rectangle (or golden ellipse), that would work too.

P.P.S.

This image gives some idea of what I'm trying to achieve. Here is the start of a tendril-drawing program, in which decisions about where to sprout a tendril, and how big, are made without regard to remaining open space. But now we want to know, where is there room to draw a tendril next, and how big? And where after that?

tendrils drawn within a circle

Community
  • 1
  • 1
LarsH
  • 27,481
  • 8
  • 94
  • 152

3 Answers3

2

One very efficient way would be to recursively divide your area into rectangular sub-areas, splitting them when necessary to divide occupied areas from unoccupied areas. Then you would simply need to keep track of the largest unoccupied area at each time. See https://en.wikipedia.org/wiki/Quadtree - but you needn't split into squares.

Given any rectangle, you can draw a line inside it, so that at least one of the rectangles to either side of the line is a golden rectangle. Therefore you can recursively erect partitions within a rectangle so that all but one of the rectangles formed by the partitions are golden rectangles, and the add rectangle left over is vanishingly small. You could do this to create a quadtree-like structure, where almost all of the rectangles left over were golden rectangles.

mcdowella
  • 19,301
  • 2
  • 19
  • 25
1

This seems like the kind of situation where a randomized algorithm might be helpful. Choose points at random, reject and choose more if they're inappropriate for some reason, then find the min distance from your choices to each of the figures already included. The random point with the max of the mins would be your choice.

The number of sample points might have to increase as the complexity of the figure increases.

The random algorithm could be improved by checking points nearby good choices. Keep checking neighbors until no more improvement is possible.

Edward Doolittle
  • 4,002
  • 2
  • 14
  • 27
  • OK. But finding the min distance from each random point to each of the figures already included could become a very expensive operation as the number of figures already included increases (exponentially with the level of detail). Do you have any thoughts on making this more tractable? (BTW I just added an image to the question.) – LarsH Jun 23 '15 at 18:44
  • Once you've picked a center at random, you can find the max radius by binary search if you can decide whether a circle contains another element or not. The example figure you gave is interesting because it's connected to the edge, so a circle contains an element if and only if it intersects the figure, which is easy to decide by checking the color of points on the circle before drawing it. Is that the case for your program? – Edward Doolittle Jun 24 '15 at 01:12
1

Here's a simple way that uses a fixed amount of memory and time per update, regardless of how many drawing primitives you use. How much memory (and time per update) is needed can be controlled according to how high a "resolution" you need:

  1. Divide the space up into a grid of points. We will maintain a 2D array, d[], which records the minimum distance from the grid point (x, y) to any already-drawn primitive in the entry d[x, y]. Initially, set every element in this array to infinity (or some huge number).
  2. Whenever you draw some primitive, iterate over all grid points (x, y) calculating the minimum distance (or some conservative approximation to it) from (x, y) to the just-drawn primitive. E.g., if the primitive just drawn was a circle of radius r centered at (p, q), then this distance would be sqrt((x-p)^2 + (y-q)^2) - r. Then update d[x, y] with this new distance value if it is smaller than its current value.
  3. The grid point at which the largest circle can be drawn without touching any already-drawn primitive is the grid point that is the farthest away from any primitive drawn so far. To find it, simply scan through d[] to find its maximum value, and note the corresponding indices (x, y). d[x, y] will be the maximum radius you could safely use for this circle.

Repeat steps 2 and 3 as necessary.

A couple of points:

  • For primitives that have area, you can assign 0 or a negative value to all d[x, y] corresponding to grid points inside the primitive.
  • For any convex primitive, you can often avoid updating most of the d[] array by scanning rows (or columns) "outward" from the just-drawn primitive's border: the distance from the current grid point to the primitive will never decrease, so if this distance becomes larger than the previous maximum value in d[] then we know that we can stop scanning this row, because no further distance value that we would compute on it could possibly be less than an existing distance on it.
j_random_hacker
  • 50,331
  • 10
  • 105
  • 169
  • I like this idea, especially with the last bullet point. It reminds me a bit of diffusion-limited aggregation (https://en.wikipedia.org/wiki/Diffusion-limited_aggregation#Artwork_based_on_diffusion-limited_aggregation). BTW I just added an illustrative image to the question. – LarsH Jun 23 '15 at 18:48
  • Maybe memory requirement could be reduced by using the least significant bits of the graphics itself. – Edward Doolittle Jun 24 '15 at 01:00
  • @EdwardDoolittle: can you elaborate on that? I don't understand how the LSBs of the graphics can be used. Do you mean reading pixel color data from the canvas/screen? – LarsH Jun 24 '15 at 13:17
  • @LarsH: I'm afraid I don't see the connection to diffusion-limited aggregation, but that might be because I have no idea how that works :) – j_random_hacker Jun 24 '15 at 15:20
  • 1
    @LarsH Yes, exactly. Memory is already allocated to represent the image. The MSBs of color data would correspond to the visible color variations of the figure. The LSBs could be used to store other information, like the distance information in the proposed answer. Depending on the color resolution, the LSB information would be more or less invisible to the eye. You would need to find out whether the color resolution is high enough, and whether the information can be read/written quickly enough. There might be variation according to browser/system. – Edward Doolittle Jun 24 '15 at 15:45