I'm trying to create a ring of circles, ala:
The circles have a given radius, circleRadius
. The ring has a maximum radius, maxRingRadius
. The number of circles can be any integer number, circles
, which needs to be calculated, along with the ring's actual radius, ringRadius
. The circles, when their centers are placed ringRadius
units from the center of the ring, should be exactly touching, as in the diagram.
Given a circleRadius
, and a maxRingRadius
, how can one find the nearest (or next smallest) ringRadius
which will fit an integer number of circles
, and then position those circles?
static Vector3[] RingOfCircles(float maxRingRadius, float circleRadius) {
//int circles = ...; // calculate this?
//float ringRadius = ...; // calculate this?
//Edit: Solution. These three lines are adapted from InBetween's GetNextSmallerRingRadius function but Unity3d-ized and without validation
int circles = Mathf.RoundToInt(Mathf.PI / Mathf.Asin(circleRadius / maxRingRadius));
float centralAngle = 2 * Mathf.PI / (numberOfCircles - 1);
float ringRadius = circleRadius / Mathf.Sin(centralAngle / 2);
// create ring of center points
float radsPerCircle = (Mathf.PI * 2) / circles;
Vector3[] centerPoints = new Vector3[circles];
for (int i=0; i < circles; i++) {
float angle = i * radsPerCircle;
centerPoints[i] = new Vector3(
Mathf.Sin(angle) * ringRadius,
Mathf.Cos(angle) * ringRadius,
0);
}
return centerPoints;
}
`
Note: maxRingRadius
could just as well be minRingRadius
or approximateRingRadius
for my purposes. But ringRadius
should define the next nearest 'ring' which can hold an whole number of circles.
Solved: Visual confirmation of InBetween's solution
If I've understood your question correctly, this should do it:
public static double GetNextSmallerRingRadius(double startingRingRadius, double circleRadius)
{
Debug.Assert(startingRingRadius >= 0);
Debug.Assert(circleRadius > 0);
int currentNumberOfCircles = GetCurrentNumberOfCircles(startingRingRadius, circleRadius);
//Let's get trivial cases out of the way
if (currentNumberOfCircles == 1)
throw new ArgumentException();
if (currentNumberOfCircles == 2)
return 0; //trivial solution for 1 circle.
if (currentNumberOfCircles == 3)
return circleRadius; //trivial solution for 2 circles.
double centralAngle = 2 * Math.PI / (currentNumberOfCircles - 1);
return circleRadius / Math.Sin(centralAngle / 2);
}
public static double GetNextLargerRingRadius(double startingRingRadius, double circleRadius)
{
Debug.Assert(startingRingRadius >= 0);
Debug.Assert(circleRadius > 0);
int currentNumberOfCircles = GetCurrentNumberOfCircles(startingRingRadius, circleRadius);
//Let's get trivial cases out of the way
if (currentNumberOfCircles == 1)
return circleRadius; //trivial solution for 2 circles.
double centralAngle = 2 * Math.PI / (currentNumberOfCircles + 1);
return circleRadius / Math.Sin(centralAngle / 2);
}
private static int GetCurrentNumberOfCircles(double startingRingRadius, double circleRadius)
{
if (startingRingRadius == 0)
{
return 1;
}
else
{
return (int)Math.Round(Math.PI / Math.Asin(circleRadius / startingRingRadius), 0); //There would need to be some logic to make sure input values are correct.
}
}
To validate the input (defined radii represent a valid solution), you can compare the rounded and not rounded numberOfcircles
and make sure that the difference is inside a given tolerance. Remember, with double
you can not check for equality as there will always be a representation error.
UPDATE Whoops, I didn't see you were also asking about positioning the circles. Once you know the ring radius and the central angle its pretty straightforward.