Optimally place a pie slice in a rectangle - algorithm
Given a rectangle (w, h) and a pie slice with a radius less or equal to the smaller of both sides (w, h), a start angle and an end angle, how can I place the slice optimally in the rectangle so that it fills the room best (from an optical point of view, not mathematically speaking)?
I'm currently placing the pie slice's center in the center of the rectangle and use the half of the smaller of both rectangle sides as the radius. This leaves plenty of room for certain configurations.
Examples to make clear what I'm after, based on the precondition that the slice is drawn like a unit circle (i.e. 0 degrees on positive X axis, then running clock-wise):
A start angle of 0 and an end angle of PI would lead to a filled lower half of the rectangle and an empty upper half. A good solution here would be to move the center up by 1/4*h.
A start angle of 0 and an end angle of PI/2 would lead to a filled bottom right quarter of the rectangle. A good solution here would be to move the center point to the top left of the rectangle and to set the radius to the smaller of both rectangle sides.
This is fairly easy for the cases I've sketched but it becomes complicated when the start and end angles are arbitrary. I am searching for an algorithm which determines center of the slice and radius in a way that fills the rectangle best. Pseudo code would be great since I'm not a big mathematician.
The extrema of the bounding box of your arc are in the following format:
x + x0 * r = 0
x + x1 * r = w
y + y0 * r = 0
y + y1 * r = h
The values x0, x1, y0 and y1 are found by taking the minimum and maximum values of up to 7 points: any tangential points that are spanned (i.e. 0, 90, 180 and 270 degrees) and the end points of the two line segments.
Given the extrema of the axis-aligned bounding box of the arc (x0, y0), (x1, y1) the radius and center point can be calculated as follows:
r = min(w/(x1-x0), h/(y1-y0)
x = -x0 * r
y = -y0 * r
Here is an implementation written in Lua:
-- ensures the angle is in the range [0, 360)
function wrap(angle)
local x = math.fmod(angle, 2 * math.pi)
if x < 0 then
x = x + 2 * math.pi
end
return x
end
function place_arc(t0, t1, w, h)
-- find the x-axis extrema
local x0 = 1
local x1 = -1
local xlist = {}
table.insert(xlist, 0)
table.insert(xlist, math.cos(t0))
table.insert(xlist, math.cos(t1))
if wrap(t0) > wrap(t1) then
table.insert(xlist, 1)
end
if wrap(t0-math.pi) > wrap(t1-math.pi) then
table.insert(xlist, -1)
end
for _, x in ipairs(xlist) do
if x < x0 then x0 = x end
if x > x1 then x1 = x end
end
-- find the y-axis extrema
local ylist = {}
local y0 = 1
local y1 = -1
table.insert(ylist, 0)
table.insert(ylist, math.sin(t0))
table.insert(ylist, math.sin(t1))
if wrap(t0-0.5*math.pi) > wrap(t1-0.5*math.pi) then
table.insert(ylist, 1)
end
if wrap(t0-1.5*math.pi) > wrap(t1-1.5*math.pi) then
table.insert(ylist, -1)
end
for _, y in ipairs(ylist) do
if y < y0 then y0 = y end
if y > y1 then y1 = y end
end
-- calculate the maximum radius the fits in the bounding box
local r = math.min(w / (x1 - x0), h / (y1 - y0))
-- find x & y from the radius and minimum extrema
local x = -x0 * r
local y = -y0 * r
-- calculate the final axis-aligned bounding-box (AABB)
local aabb = {
x0 = x + x0 * r,
y0 = y + y0 * r,
x1 = x + x1 * r,
y1 = y + y1 * r
}
return x, y, r, aabb
end
function center_arc(x, y, aabb, w, h)
dx = (w - aabb.x1) / 2
dy = (h - aabb.y1) / 2
return x + dx, y + dy
end
t0 = math.rad(60)
t1 = math.rad(300)
w = 320
h = 240
x, y, r, aabb = place_arc(t0, t1, w, h)
x, y = center_arc(x, y, aabb, w, h)
print(x, y, r)
Example output:
Instead of pseudo code, I used python, but it should be usable. For this algorithm, I assume that startAngle < endAngle and that both are within [-2 * PI, 2 * PI]. If you want to use both within [0, 2 * PI] and let startAngle > endAngle, I would do:
if (startAngle > endAngle):
startAngle = startAngle - 2 * PI
So, the algorithm that comes to mind is to calculate the bounds of the unit arc and then scale to fit your rectangle.
The first is the harder part. 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, angle is a range, so it's not as simple as this. However, you really only have to include up to 11 points in this calculation. The start angle, the end angle, and potentially, the cardinal directions (there are 9 of these going from -2 * PI to 2 * PI.) I'm 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've computed the bounding box of an arc with center of 0,0 and radius of 1. To fill the box, we're going to have to solve a 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 solve for xRadius and yRadius. You'll note there are two radiuses here. The reason for that is that in order to fill the rectangle, we have to multiple by different amounts in the two directions. Since your algorithm asks for only one radius, we will just pick the lower of the two values.
Solving the equation gives:
xRadius = w / (boundingBox[1] - boundingBox[0])
yRadius = h / (boundingBox[2] - boundingBox[3])
radius = MIN(xRadius, yRadius)
Here, you have to check for boundingBox[1] - boundingBox[0] being 0 and set xRadius to infinity in that case. This will give the correct result as yRadius will be smaller. If you don't have an infinity available, you can just set it to 0 and in the MIN function, check for 0 and use the other value in that case. xRadius and yRadius can't both be 0 because both sin and cos would have to be 0 for all angles included above for that to be the case.
Now we have to place the center of the arc. We want it centered in both directions. Now we'll create another linear equation:
(boundingBox[0] + boundingBox[1]) / 2 * radius + x = xCenter = w/2
(boundingBox[2] + boundingBox[3]) / 2 * radius + y = yCenter = h/2
Solving 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 put the largest circle in the given rectangle.
I haven't tested any of this code, so this algorithm may have huge holes, or perhaps tiny ones caused by typos. I'd love to know if this algoritm works.
edit:
Putting all of the code together 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 issue here is that sin[2 * PI] is not going to be exactly 0 because of 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)
Just consider a circle and forget the filling. The bounds will either be the center of the circle, the endpoints, or the points at 0, 90, 180, or 270 degrees (if they exist in this slice). The maxima and minima of these seven points will determine your bounding rectangle.
As far as placing it in the center, calculate the average of the max and min for both the rectangle and the pie slice, and add or subtract the difference of these to whichever one you want to move.
I would divide the problem into three steps:
Find the bounding box of a unit pie slice (or if a radius is given the actual pie slice centered at (0, 0)).
Fit the bounding box in your rectangle.
Use the information about fitting the bounding box to adjust the center and radius of the pie slice.
When I have time, I may flush this out with more details.
Related
Algorithm to detect when and where a point will exit a rectangle area
Assume that we have a rectangle or a square and we know the x,y coordinates of its corners (4 corners). Also assume that we have a point inside that square for which we know its coordinates (x,y), its speed (km/h), its heading (heading is measured in directional degrees, 0 for north, 180 for south and so on) and the time point it has these attributes (epoch time in seconds). How can we calculate the time point (epoch time in seconds) in which the point will exit the rectangle as well as the coordinates (x,y) of the exit ?
You need to find what edge is intersected first. Make equations for moving along both coordinates and calculate the first time of intersection. Note that for geographic coordinates you might need more complex calculations because "rectangle" defined by Lat/Lon coordinates is really curvy trapezoid on the Earth surface. Look at "Intersection of two paths given start points and bearings" chapter on this page to get travel time. vx = V * Cos(heading + Pi/2) //for y-axis north=0 vy = V * Sin(heading + Pi/2) x = x0 + vx * t y = y0 + vy * t //potential border positions if vx > 0 then ex = x2 else ex = x1 if vy > 0 then ey = y2 else ey = y1 //check for horizontal/vertical directions if vx = 0 then return cx = x0, cy = ey, ct = (ey - y0) / vy if vy = 0 then return cx = ex, cy = y0, ct = (ex - x0) / vx //in general case find times of intersections with horizontal and vertical edge line tx = (ex - x0) / vx ty = (ey - y0) / vy //and get intersection for smaller parameter value if tx <= ty then return cx = ex, cy = y0 + tx * vy, ct = tx else return cx = x0 + ty * vx, cy = ey, ct = ty
Finding the coordinates x y z of the center of a surface
I have a surface in a 3D space, and I need to calculate the coordinates of the center of that surface. The surface is a polygon. I found this formula: X1 += SUM[(xi + xi+1 ) * (xi* yi+1 - xi+1 * yi )]/6/area Y1 += SUM[(yi + yi+1) * (xi* yi+1 - xi+1 * yi )]/6/area But it only works for 2D. It gives x and y values. In my case I need 3 coordinates, x y z. How can I do that ? I need the center of the surfaces selected, but they may have any shape as they are a polygon. Thanks a lot.
It's easy to prove that the centroid of the projection of a polygon on any plane is the projection of the centroid of that polygon on that plane. So just calculate the centroid of the projections of the polygon on x-y plane and y-z plane, and you'll get the coordinate of the centroid of that polygon. #!/usr/bin/env ruby Point = Struct.new(:x, :y, :z) def centroid(vertices, dimensions: [:x, :y]) area = 0 centroid_x, centroid_y = 0, 0 vertices.count.times do |i| v1, v2 = vertices[i, 2] v2 ||= vertices[0] x1, y1 = dimensions.map{|d| v1.send(d)} x2, y2 = dimensions.map{|d| v2.send(d)} a = x1 * y2 - x2 * y1 area += a centroid_x += (x1 + x2) * a centroid_y += (y1 + y2) * a end area *= 0.5 centroid_x /= (6.0 * area) centroid_y /= (6.0 * area) [centroid_x, centroid_y] end vertices = [ Point.new(1, 0, 0), Point.new(0, 2, 0), Point.new(0, 0, 3) ] p centroid(vertices, dimensions: [:x, :y]) p centroid(vertices, dimensions: [:y, :z]) p centroid(vertices, dimensions: [:z, :x]) prints [0.3333333333333333, 0.6666666666666666] [0.6666666666666666, 1.0] [1.0, 0.3333333333333333]
#Aetherus it didn't work for my example: X1 = 0 Y1 = 0 Y11 = 0 Z1 = 0 for i in 0..d.vertices.size-2 X1 += (d.vertices[i].position[0] + d.vertices[i+1].position[0]) * (d.vertices[i].position[0] * d.vertices[i+1].position[1] - d.vertices[i+1].position[0] * d.vertices[i].position[1]) Y1 += (d.vertices[i].position[1] + d.vertices[i+1].position[1]) * (d.vertices[i].position[0] * d.vertices[i+1].position[1] - d.vertices[i+1].position[0] * d.vertices[i].position[1]) Y11 += (d.vertices[i].position[1] + d.vertices[i+1].position[1]) * (d.vertices[i].position[1] * d.vertices[i+1].position[2] - d.vertices[i+1].position[1] * d.vertices[i].position[2]) Z1 += (d.vertices[i].position[2] + d.vertices[i+1].position[2]) * (d.vertices[i].position[1] * d.vertices[i+1].position[2] - d.vertices[i+1].position[1] * d.vertices[i].position[2]) end x=X1/(6.0*(d.area)) y=Y1/(6.0*(d.area)) y1=Y11/(6.0*(d.area)) z=Z1/(6.0*(d.area)) UI.messagebox("x1 #{x} Y1 #{y} \n y11 #{y1} z1 #{z}") With "d" is my polygon the xy is for x-y projection and y1z for y-z projection.
Calculating a location on a circle given an angle of rotation
Okay algebra and trig are not my strong suit by any means so here is what I need to do. I have a circle which is measured in degrees from +180 to -180 (360 total) Given the center point of the circle stays the same, Cx , Cy. The angle varies from -180 to +180 I need to locate a point that regardless the given angle is + 3 units away that is at the 90 degree position and the 270 degree position (from the given degrees) So like... Angle = 0 Point 1 -> x = 0, y -3 Point 2 -> x = 0, y + 3 And if the angle was say 90 (provided its measured Clockwise) Point 1 -> x = -3, y = 0 Point 2 -> x = 3, y = 0 What I need is a forumla that will accept Angle, then tell me what my x/y should be 3 units away from the origin. I have tried: EDIT Updated to double precision using Java. `double x = Cx + 3 * Math.cos((d + 90) * Math.PI / 180);' 'double y = Cy + 3 * Math.sin((d + 90) * Math.PI / 180);` this gives me mixed results, I mean sometimes it's where I think it should be and other times its quite wrong. Assuming Cx = 0.500, Cy = 0.500 Sample Data: Result: Deg = 0 x = 2 / y = 5 Deg = 90 x = -1 / y = 2 Deg = 125 x = -0.457 / y = 0.297 Deg = 159 x = 0.924 / y = -0.800 I realize I am only calculating one point at this point but do you have any suggestions on how to get the first point working? at say 90 degrees from whatever degree I start with?
x = Cx + r * Math.cos( (d+90) * Math.PI / 180 ); y = Cy + r * Math.sin( (d+90) * Math.PI / 180 ); Seems that this is the correct formula for what I was trying to accomplish. This will take any value for Cx/Cy's origin add the Radius r, then calculate the degrees + 90 and convert to radians.. Once all that magic takes place, you're left with an x/y coord that is 90 degrees of where you started.
How do I translate and scale points within a bounding box?
I have a number of points P of the form (x, y) where x,y are real numbers. I want to translate and scale all these points within a bounding box (rectangle) which begins at the point (0,0) (top left) and extends to the point (1000, 1000) (bottom right). Why is it that the following algorithm does not produce points in that bounding box? for Point p in P: max = greatest(p.x, p.y, max) scale = 1000 / max for Point p in P: p.x = (p.x - 500) * scale + 500 p.y = (p.y - 500) * scale + 500 I fear that this won't work when p.x or p.y is a negative number. I would also like to maintain the "shape" of the points.
Find all of yMin, yMax, xMin, xMax, xDelta = xMax-xMin and yDelta = yMax-yMin for your set of points. Set max = greatest(xDelta,yDelta). Foreach Point p set p.X = (p.X - xMin) * scale and p.Y = (p.Y - yMin) * scale
2D bounding box of a sector?
I've googled till I'm blue in the face, and unless I'm missing something really obvious, I can't find any algorithms for calculating the bounding box of a 2D sector. Given the centre point of the enclosing circle, the radius, and the angles of the extent of the sector, what's the best algorithm to calculate the axis-aligned bounding rectangle of that sector?
Generate the following points: The circle's center The positions of the start and end angles of the sector Additionally, for the angles among 0, 90, 180, and 270 that are within the angle range of the sector, their respective points on the sector Calculate the min and max x and y from the above points. This is your bounding box
I'm going to rephrase yairchu's answer so that it is clearer (to me, anyway). Ignore the center coordinates for now and draw the circle at the origin. Convince yourself of the following: Anywhere the arc intersects an axis will be a max or a min. If the arc doesn't intersect an axis, then the center will be one corner of the bounding rectangle, and this is the only case when it will be. The only other possible extreme points of the sector to consider are the endpoints of the radii. You now have at most 4+1+2 points to find. Find the max and min of those coordinates to draw the rectangle. The rectangle is easily translated to the original circle by adding the coordinates of the center of the original circle to the rectangle's coordinates.
First of all I apologize if I commit mistakes writing but english is not my first language, spanish is actually! I faced this problem, and I think I found an efficient solution. First of all let's see an image of the situation So we have an ellipse (actually a circle) and two points (C, D) which indicates our sector. We also have the center of our circle (B) and the angle of the Arc alpha. Now, in this case I made it passing through 360º on porpouse to see if it would work. Let's say alpha -> -251.1º (it negative cause its clockwise), lets transform it to positive value 360º - 251.1º = 108.9º now our goal is to find the angle of the bisection of that angle so we can find the max point for the bounding box (E in the image), actually as you may have realized, the length of the segment BE equals the radius of the circle but we must have the angle to obtain the actual coordinates of the E point. So 108.9º / 2 -> 54.45º now we have the angle. To find the coordinates of E we use polar coordinates so x = r * Cos(theta) y = r * Sin(theta) we have r and theta so we can calculate x and y in my example r = 2.82… (actually it's irational but I took the first two decimal digits as a matter of ease) We know our first radii is 87.1º so theta would be 87.1 - 54.45º -> 32.65º we know *theta * is 32.65º so let's do some math x = 2.82 * Cos(32.65º) -> 2.37552 y = 2.82 * Sin(32.65º) -> 1.52213 Now we need to adjust these values to the actual center of the circle so x = x + centerX y = y + centerY In the example, the circle is centered at (1.86, 4.24) x -> 4.23552 y -> 5.76213 At this stage we should use some calculus. We know that one of the edges of the bounding box will be a tangent of the arc that passes through the point we just calculated so, lets find that tangent (the red line). We know that the tangent passes through our point (4.23, 5.76) now we need a slope. As you can see, the slope is the same as the slope of the rect that passes through our radii's so we have to find that slope. For doing that we need to get the coordinates of our radii's (a fast conversion to cartessian coordinates from polar coordinates). x = r * Cos(theta) y = r * Sin(theta) So p0 = (centerX + 2.82 * Cos(87.1º), centerY + 2.82 * Sin(87.1º)) p1 = (centerX + 2.82 * Cos(-21.8º), centerY + 2.82 * Sin(-21.8º)) (21.8º is the angle measured clockwise from the horizontal axis to the radii that is below it and thus I put it negative) p0 (2, 7.06) p1 (4.48, 3.19) now let's find the slope: m = (y - y0) / (x - x0) ... m = (3.19 - 7.06) / (4.48-2) = -3.87 / 2.48 = -1.56048 ... m = -1.56 having the slope we need to calculate the equation for the tangent, basically is a rect with an already known slope (m = -1.56) that passes through an already know point (E -> (4.23, 5.76)) So we have Y = mx + b where m = -1.56, y = 5.76 and x = 4.23 so b must be b = 5.76 - (-1.56) * 4.23 = 12.36 Now we have the complete equation for our tangent -> Y = -1.56X + 12.36 All we must do know is project the points C and D over that rect. We need the equations for the rects CH and DI so let's calculate 'em Let's start with CH: We know (from the tanget's equation) that our direction vector is (1.56, 1) We need to find a rect that passes through the point C -> (2, 7.06) (x - 2) / 1.56 = (y - 7.06) / 1 Doing some algebra -> y = 0.64x + 5.78 We know have the equation for the rect CH we must calculate the point H. we have to solve a linear system as follows y = -1.56x + 12.36 y = 1.56x + 5.78 Solving this we'll find the point H (3, 7.69) We need to do the same with the rect DI so let's do it Our direction vector is (1.56, 1) once again D -> (4.48, 3.19) (x - 4.48) / 1.56 = (y -3.19) / 1 Doing some algebra -> y = 0.64x + 0.32 Lets solve the linear system y = -1.56x + 12.36 y = 0.64x + 0.32 I (5.47, 3.82) At this stage we already have the four points that make our Bounding box -> C, H, D , I Just in case you don't know or rememeber how to solve a linear system on a programming language, i'll give you a little example It's pure algebra Let's say we have the following system Ax + By = C Dx + Ey = F then Dx = F - Ey x = (F - Ey) / D x = F/D - (E/D)y replacing on the other equation A(F/D - (E/D)y) + By = C AF/D - (AE/D)y + By = C (AE/D)y + By = C - AF/D y(-AE/D + B) = C - AF/D y = (C - AF/D) / (-AE/D + B) = ( (CD - AF) / D ) / ( (-AE + BD) / D) ) so y = (CD - AF) / (BD - AE) and for x we do the same Dx = F - Ey Dx - F = -Ey Ey = F - Dx y = F/E - (D/E)x replacing on the other equation Ax + B(F/E - (D/E)x) = C Ax + (BF/E - (DB/E)x) = C Ax - (DB/E)x = C - BF/E x (A-(DB/E)) = C - BF/E x = (C - BF/E)/(A-(DB/E)) = ((CE - BF) / E) / ((AE-DB) / E) x = (CE - BF) / (AE - DB) I apologize for the extent of my answer but I meant to be as clear as possible and thus, I made it almost step by step.
In C# code: /// <summary> /// The input parameters describe a circular arc going _clockwise_ from E to F. /// The output is the bounding box. /// </summary> public Rect BoundingBox(Point E, Point F, Point C, double radius) { // Put the endpoints into the bounding box: double x1 = E.X; double y1 = E.Y; double x2 = x1, y2 = y1; if (F.X < x1) x1 = F.X; if (F.X > x2) x2 = F.X; if (F.Y < y1) y1 = F.Y; if (F.Y > y2) y2 = F.Y; // Now consider the top/bottom/left/right extremities of the circle: double thetaE = Math.Atan2(E.Y - C.Y, E.X - C.X); double thetaF = Math.Atan2(F.Y - C.Y, F.X - C.X); if (AnglesInClockwiseSequence(thetaE, 0/*right*/, thetaF)) { double x = (C.X + radius); if (x > x2) x2 = x; } if (AnglesInClockwiseSequence(thetaE, Math.PI/2/*bottom*/, thetaF)) { double y = (C.Y + radius); if (y > y2) y2 = y; } if (AnglesInClockwiseSequence(thetaE, Math.PI/*left*/, thetaF)) { double x = (C.X - radius); if (x < x1) x1 = x; } if (AnglesInClockwiseSequence(thetaE, Math.PI*3/2/*top*/, thetaF)) { double y = (C.Y - radius); if (y < y1) y1 = y; } return new Rect(x1, y1, x2 - x1, y2 - y1); } /// <summary> /// Do these angles go in clockwise sequence? /// </summary> private static bool AnglesInClockwiseSequence(double x, double y, double z) { return AngularDiffSigned(x, y) + AngularDiffSigned(y, z) < 2*Math.PI; } /// <summary> /// Returns a number between 0 and 360 degrees, as radians, representing the /// angle required to go clockwise from 'theta1' to 'theta2'. If 'theta2' is /// 5 degrees clockwise from 'theta1' then return 5 degrees. If it's 5 degrees /// anticlockwise then return 360-5 degrees. /// </summary> public static double AngularDiffSigned(double theta1, double theta2) { double dif = theta2 - theta1; while (dif >= 2 * Math.PI) dif -= 2 * Math.PI; while (dif <= 0) dif += 2 * Math.PI; return dif; }
I tried to implement jairchu's answer, but found some problems, which I would like to share: My coordinate system for the circle starts with 0 degrees at the right side of the circle and runs counterclockwise through the top (90deg), the left(180deg) and the bottom (270deg). The angles can be between 0 and 359,9999 deg. The center point should not be part of the list of points You have to distinguish between clockwise and counterclockwise arcs in order to make the list of points that lie on 0,90,180,270 deg It is tricky to determine if the angle span includes the angle 0,90,180 or 270 deg. public override Rect Box() { List<Point> potentialExtrema = new List<Point>(); potentialExtrema.Add(StartPoint); potentialExtrema.Add(EndPoint); if (!ClockWise) { if (EndAngle < StartAngle || EndAngle == 0 || StartAngle == 0 || EndAngle == 360 || StartAngle == 360) potentialExtrema.Add(new Point(Point.X + Radius, Point.Y)); if ((StartAngle <= 90 || StartAngle > EndAngle) && EndAngle >= 90) potentialExtrema.Add(new Point(Point.X, Point.Y + Radius)); if ((StartAngle <= 180 || StartAngle > EndAngle) && EndAngle >= 180) potentialExtrema.Add(new Point(Point.X - Radius, Point.Y)); if ((StartAngle <= 270 || StartAngle > EndAngle) && EndAngle >= 270) potentialExtrema.Add(new Point(Point.X, Point.Y - Radius)); } else { if (StartAngle < EndAngle || EndAngle == 0 || StartAngle == 0 || EndAngle == 360 || StartAngle == 360) potentialExtrema.Add(new Point(Point.X + Radius, Point.Y)); if ((StartAngle >= 90 || StartAngle < EndAngle) && EndAngle <= 90) potentialExtrema.Add(new Point(Point.X, Point.Y + Radius)); if ((StartAngle >= 180 || StartAngle < EndAngle) && EndAngle <= 180) potentialExtrema.Add(new Point(Point.X - Radius, Point.Y)); if ((StartAngle >= 270 || StartAngle < EndAngle) && EndAngle <= 270) potentialExtrema.Add(new Point(Point.X, Point.Y - Radius)); } double maxX = double.NegativeInfinity; double maxY = double.NegativeInfinity; double minX = double.PositiveInfinity; double minY = double.PositiveInfinity; foreach (var point in potentialExtrema) { if (point.X > maxX) maxX = point.X; if (point.Y > maxY) maxY = point.Y; if (point.X < minX) minX = point.X; if (point.Y < minY) minY = point.Y; } return new Rect(minX, minY, maxX - minX, maxY - minY); } } There is a more elegant solution determining wether 0,90,180 or 270 deg lie within the angle span: public override Rect Box() { List<Point> potentialExtrema = new List<Point>(); potentialExtrema.Add(StartPoint); potentialExtrema.Add(EndPoint); if (AngleProduct(0)) potentialExtrema.Add(new Point(Point.X + Radius, Point.Y)); if (AngleProduct(90)) potentialExtrema.Add(new Point(Point.X, Point.Y + Radius)); if (AngleProduct(180)) potentialExtrema.Add(new Point(Point.X - Radius, Point.Y)); if (AngleProduct(270)) potentialExtrema.Add(new Point(Point.X, Point.Y - Radius)); double maxX = double.NegativeInfinity; double maxY = double.NegativeInfinity; double minX = double.PositiveInfinity; double minY = double.PositiveInfinity; foreach (var point in potentialExtrema) { if (point.X > maxX) maxX = point.X; if (point.Y > maxY) maxY = point.Y; if (point.X < minX) minX = point.X; if (point.Y < minY) minY = point.Y; } return new Rect(minX, minY, maxX - minX, maxY - minY); } private bool AngleProduct(int alpha) { if (StartAngle == EndAngle) if (StartAngle == alpha) return true; else return false; double prod = 0; if (ClockWise) prod = -1 * (alpha - StartAngle) * (EndAngle - alpha) * (EndAngle - StartAngle); else prod = (alpha - StartAngle) * (EndAngle - alpha) * (EndAngle - StartAngle); if (prod >= 0) return true; else return false; }