Instead of pseudo code, I used python, but it should be used. For this algorithm, I assume that startAngle < endAngle and that both are inside [-2 * PI, 2 * PI] . If you want to use both inside [0, 2 * PI] and startAngle> endAngle, I would do:
if (startAngle > endAngle): startAngle = startAngle - 2 * PI
So, the algorithm that comes to mind is to calculate the boundaries of the unit arc, and then scale to fit your rectangle.
Firstly, it is more complicated. You need to calculate 4 numbers:
Left: MIN(cos(angle), 0) Right: MAX(cos(angle), 0) Top: MIN(sin(angle),0) Bottom: MAX(sin(angle),0)
Of course, an angle is a range, so it is not as simple as this. However, in this calculation you need to include up to 11 points. Starting angle, ending angle, and possibly cardinal directions (of which 9 are from -2 * PI to 2 * PI .) I am going to define boundingBoxes as lists of 4 elements ordered [left, right, top, bottom]
def IncludeAngle(boundingBox, angle) x = cos(angle) y = sin(angle) if (x < boundingBox[0]): boundingBox[0] = x if (x > boundingBox[1]): boundingBox[1] = x if (y < boundingBox[2]): boundingBox[2] = y if (y > boundingBox[3]): boundingBox[3] = y def CheckAngle(boundingBox, startAngle, endAngle, angle): if (startAngle <= angle and endAngle >= angle): IncludeAngle(boundingBox, angle) boundingBox = [0, 0, 0, 0] IncludeAngle(boundingBox, startAngle) IncludeAngle(boundingBox, endAngle) CheckAngle(boundingBox, startAngle, endAngle, -2 * PI) CheckAngle(boundingBox, startAngle, endAngle, -3 * PI / 2) CheckAngle(boundingBox, startAngle, endAngle, -PI) CheckAngle(boundingBox, startAngle, endAngle, -PI / 2) CheckAngle(boundingBox, startAngle, endAngle, 0) CheckAngle(boundingBox, startAngle, endAngle, PI / 2) CheckAngle(boundingBox, startAngle, endAngle, PI) CheckAngle(boundingBox, startAngle, endAngle, 3 * PI / 2) CheckAngle(boundingBox, startAngle, endAngle, 2 * PI)
Now you have calculated the bounding box of the arc with the center 0,0 and radius 1 . To fill in the field, we will need to solve the linear equation:
boundingBox[0] * xRadius + xOffset = 0 boundingBox[1] * xRadius + xOffset = w boundingBox[2] * yRadius + yOffset = 0 boundingBox[3] * yRadius + yOffset = h
And we have to decide for xRadius and yRadius. You will notice that there are two radii. The reason for this is that in order to fill the rectangle, we must have several differences in two directions. Since your algorithm only requests one radius, we simply select the bottom of the two values.
The solution of the equation gives:
xRadius = w / (boundingBox[1] - boundingBox[0]) yRadius = h / (boundingBox[2] - boundingBox[3]) radius = MIN(xRadius, yRadius)
Here you need to check boundingBox[1] - boundingBox[0] to 0 and set xRadius to infinity in this case. This will give the correct result, since yRadius will be smaller. If you don't have infinity, you can just set it to 0 and in the MIN function, check 0 and use a different value in this case. xRadius and yRadius cannot be 0 , because for sin and cos must be 0 for all angles included above.
Now we have to put the center of the arc. We want this to be concentrated in both directions. Now we will create another linear equation:
(boundingBox[0] + boundingBox[1]) / 2 * radius + x = xCenter = w/2 (boundingBox[2] + boundingBox[3]) / 2 * radius + y = yCenter = h/2
The solution for x and y , the center of the arc, gives
x = w/2 - (boundingBox[0] + boundingBox[1]) * radius / 2 y = h/2 - (boundingBox[3] + boundingBox[2]) * radius / 2
This should give you the center of the arc and the radius needed to fit the largest circle in the given rectangle.
I have not tested any of this code, so this algorithm can have huge holes or possibly tiny ones caused by typos. I would really like to know if this algorithm works.
edit:
Entering the whole code gives:
def IncludeAngle(boundingBox, angle) x = cos(angle) y = sin(angle) if (x < boundingBox[0]): boundingBox[0] = x if (x > boundingBox[1]): boundingBox[1] = x if (y < boundingBox[2]): boundingBox[2] = y if (y > boundingBox[3]): boundingBox[3] = y def CheckAngle(boundingBox, startAngle, endAngle, angle): if (startAngle <= angle and endAngle >= angle): IncludeAngle(boundingBox, angle) boundingBox = [0, 0, 0, 0] IncludeAngle(boundingBox, startAngle) IncludeAngle(boundingBox, endAngle) CheckAngle(boundingBox, startAngle, endAngle, -2 * PI) CheckAngle(boundingBox, startAngle, endAngle, -3 * PI / 2) CheckAngle(boundingBox, startAngle, endAngle, -PI) CheckAngle(boundingBox, startAngle, endAngle, -PI / 2) CheckAngle(boundingBox, startAngle, endAngle, 0) CheckAngle(boundingBox, startAngle, endAngle, PI / 2) CheckAngle(boundingBox, startAngle, endAngle, PI) CheckAngle(boundingBox, startAngle, endAngle, 3 * PI / 2) CheckAngle(boundingBox, startAngle, endAngle, 2 * PI) if (boundingBox[1] == boundingBox[0]): xRadius = 0 else: xRadius = w / (boundingBox[1] - boundingBox[0]) if (boundingBox[3] == boundingBox[2]): yRadius = 0 else: yRadius = h / (boundingBox[3] - boundingBox[2]) if xRadius == 0: radius = yRadius elif yRadius == 0: radius = xRadius else: radius = MIN(xRadius, yRadius) x = w/2 - (boundingBox[0] + boundingBox[1]) * radius / 2 y = h/2 - (boundingBox[3] + boundingBox[2]) * radius / 2
edit:
One of the problems is that sin[2 * PI] will not be exactly 0 due to rounding errors. I think the solution is to get rid of the CheckAngle calls and replace them with something like:
def CheckCardinal(boundingBox, startAngle, endAngle, cardinal): if startAngle < cardinal * PI / 2 and endAngle > cardinal * PI / 2: cardinal = cardinal % 4 if cardinal == 0: boundingBox[1] = 1 if cardinal == 1: boundingBox[3] = 1 if cardinal == 2: boundingBox[0] = -1 if cardinal == 3: boundingBox[2] = -1 CheckCardinal(boundingBox, startAngle, endAngle, -4) CheckCardinal(boundingBox, startAngle, endAngle, -3) CheckCardinal(boundingBox, startAngle, endAngle, -2) CheckCardinal(boundingBox, startAngle, endAngle, -1) CheckCardinal(boundingBox, startAngle, endAngle, 0) CheckCardinal(boundingBox, startAngle, endAngle, 1) CheckCardinal(boundingBox, startAngle, endAngle, 2) CheckCardinal(boundingBox, startAngle, endAngle, 3) CheckCardinal(boundingBox, startAngle, endAngle, 4)
You still need IncludeAngle(startAngle) and IncludeAngle(endAngle)