Search code examples
c#unity-game-engine2dtrigonometryunity3d-2dtools

How to position a ring of circles with a maximum ring radius


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: A visual confirmation of InBetween's solution Visual confirmation of InBetween's solution


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.