How to make a circle within another circle?

693 Views Asked by At

I'm coding a "storm" for a battle royale map, and I'm trying to make a new safe zone (a perfect circle) inside of a preexisting circle.

Right now, it looks like this: Link to circle picture

This, as you can see, is a big bug in my code, which is:

cir = new Circle(random.nextInt(cir.Width-cir.X)+cir.X, random.nextInt(Math.abs(cir1.Height-cir.Y))+cir.Y, 270, 270, 135, Color.cyan, true);

cir1 is a preexisting Circle I created, which I am changing the values of.
cir is the Circle I created before (e.g. for Safe Zone 3, cir1 is the new Circle, and cir is Safe Zone 2.

My math to get the circles' $x$'s and $y$'s explained:
The math is: $rndInt(d-x)+x$ and $rndInt(|d-y|)+y$.
$d$ is the diameter of the circle, $x$ is the left x value, and $y$ is the top y of the circle.

This - in theory - should work, but it doesn't, as seen in the image.

In it, I basically get a random number between $0$ and the difference of the diameter and x.
From there, I add it to x. In theory, this will make a number and add it to x, making it between the preexisting circle.

Is there anything I'm doing wrong? I've spent weeks trying to figure out a formula, and this is the best I've got.

1

There are 1 best solutions below

2
On BEST ANSWER

I am not absolutely sure if I understood your problem statement correctly, so here is the question as I understood it:

How do I ensure that a randomly generated circle is completely contained in an existing circle?

Let's assume the existing circle is centered at $(X, Y)$ and has radius $R = D/2$, where $D$ is its diameter.

First, pick a radius $r$, $0 \lt r \le R$ for the new circle.

Then, pick an uniform random angle $0\text{°} \le \varphi \lt 360\text{°}$, or $-180\text{°} \le \varphi \lt 180\text{°}$, or $0 \le \varphi \lt 2\pi$ in radians.

Finally, pick an uniform random deviation $d$ from center, $0 \le d \le R - r$.

The center of the new smaller circle is then $$\bbox{ \left\lbrace \; \begin{aligned} x &= X + d \cos \varphi \\ y &= Y + d \sin \varphi \\ \end{aligned} \right . }$$

The reason for using trigonometric functions instead of the axis-aligned bounding box is that you can fit a small circle completely within the bounding box of a larger circle, without overlapping at all with the larger circle. Using the radius, and choosing the direction from the center of the larger circle randomly, avoids that sort of error completely.

In pseudocode (in no particular programming language), you could implement this using

Function RandomCircleInside(centerX, centerY, radius):
    Let  newRadius = radius * Random()
    Let  radians = 2.0 * 3.14159265358979323846 * Random()
    Let  deviation = (radius - newRadius) * Sqrt(Random())
    Let  newX = centerX + deviation * Cos(radians)
    Let  newY = centerY + deviation * Sin(radians)
    Return (newX, newY, newRadius)
End Function

where Random() is a function that returns an uniform random floating-point number between 0 and 1.0, inclusive (meaning it can return 0 and 1); and Sin() and Cos() are the respective trigonometric functions, with argument in radians; and Sqrt() is the square root operation. The square root is needed to ensure an uniform distribution within the disc.

If you want, you can also supply the newRadius parameter to the function (and not calculate it from radius * Random()); the function will work as long as newRadiusradius.

If you cannot use trigonometric functions, we can rewrite the function as

Function RandomCircleInside(centerX, centerY, radius):
    Let  newRadius = radius * Random()
    Let  deviation = (radius - newRadius) * Random()
    Let  dd = deviation * deviation

    Do:
         dX = deviation - 2 * deviation * Random()
         dY = deviation - 2 * deviation * Random()
    While (dX*dX + dY*dY > dd)

    Let  newX = centerX + dX
    Let  newY = centerY + dY
    Return (newX, newY, newRadius)
End Function

This is just as good as the trigonometric function using one, except that it calls Random() about 27% "extra times" on average. (This uses the "exclusion method" to generate a random point within a circular disc. It generates a random point within the axis-aligned square that contains the disc, but ignores any point outside the disc. Since the ratio of their areas is $4 / \pi \approx 1.27324$, the do-while loop iterates 1.27324 times on average. That is, most times it only does one iteration, and sometimes more than one.)

If your circles are determined by their axis-aligned bounding box, note that the left edge (minimum $x$) is at $X - R$; right edge (maximum $x$) at $X + R$; top edge (minimum $y$) is at $Y - R$; and bottom edge (maximum $y$) at $Y + R$.

If you define your circles based on their axis-aligned bounding box, then center X is (right + left) / 2 = left + width/2, center Y is (bottom + top) / 2 = top + height/2, and radius is (right - left) / 2 = (bottom - top) / 2 = (right - left + bottom - top) / 4 = width / 2 = height / 2 = (width + height) / 2.