Fit a rectangle into another rectangle - algorithm

I'm frequently fitting one rectangle into another so that it fits nicely and is centered.
I would draw something on the whiteboard and take a picture of what the logic is but it's getting dark and candle light makes it not as fun to do.
Anyway, it's quite straightforward and easy to understand. Here is the function I just had to write out from scratch again (this time in PHP):
// Fit rectangle 2 into rectangle 1 to get rectangle 3
// Rectangle 3 must be centered
// Return dimensions of rectangle and position relative to rectangle 1
function fitrect($w1,$h1,$w2,$h2){
// Let's take a chance with rectangle 3 width being equal to rectangle 1 width
$w3=$w1;
$h3=$w3*($h2/$w2);
// Check if height breaks rectangle 1 height
if($h3>$h1){
// Recalculate dimensions and then position
$h3=$h1;
$w3=$h3*($w2/$h2);
$x3=($w1-$w3)/2;
$y3=0;
}else{
// Just calculate position
$y3=($h1-$h3)/2;
$x3=0;
}
// Tidy up
$x3=round($x3);
$y3=round($y3);
$w3=round($w3);
$h3=round($h3);
// Result array
$res=array($x3,$y3,$w3,$h3);
return($res);
}
I'd like to understand this algorithm and other versions of it so that my brain groks the foundations so that I never have to rely on pen and paper (or the whiteboard) again.
So, how would you do this? What fluff can be removed?
EDIT: As an example - say we have rectangle 1 to have dimensions 256x256 and rectangle 2 to be 44x167. Then we need to scale rectangle 2 to 67x256 and position it at 94,0 relative to rectangle 1 so that it sits maximized and centralized in rectangle 1.

Here's how I would do it.
let's define a term, fatness, that is equal to the ratio of a rectangle's width to its height. a rectangle with height 1 and width 10 has fatness 10. A rectangle with height 20 and width 10 has fatness 0.5. When you resize a rectangle, its fatness doesn't change.
When you scale rectangle 2 up or down in size so that its width is equal to rectangle 1, it will not overflow the top or bottom as long as rectangle 2 is fatter than rectangle 1. It will overflow if 1 is fatter than 2. Now you know ahead of time whether to resize for a snug width, or a snug height. Furthermore, the translation logic is the same for both cases, so that can go outside of the if/else block.
in pseudocode: (sorry, I don't know PHP)
fatness1 = w1 / h1
fatness2 = w2 / h2
#adjust scaling
if fatness2 >= fatness1:
#scale for a snug width
scaleRatio = w1 / w2
else:
#scale for a snug height
scaleRatio = h1 / h2
w3 = w2 * scaleRatio
h3 = h2 * scaleRatio
#adjust rectangle 3's center so it is the same as 1's center
xCenterOf1 = x1 + (w1 / 2)
yCenterOf1 = y1 + (h1 / 2)
x3 = xCenterOf1 - (w3 / 2)
y3 = yCenterOf1 - (h3 / 2)
return (x3, y3, w3, h3)
Testing in python, assuming rectangle 1 is at (0,0), scale(256,256, 44, 167) returns (0.0, 94.3, 256.0, 67.4).

Here's a handy function written in Java.
public static RectF fitRectWithin(Rect inner, Rect outer) {
float innerAspectRatio = inner.width() / (float) inner.height();
float outerAspectRatio = outer.width() / (float) outer.height();
float resizeFactor = (innerAspectRatio >= outerAspectRatio) ?
(outer.width() / (float) inner.width()) :
(outer.height() / (float) inner.height());
float newWidth = inner.width() * resizeFactor;
float newHeight = inner.height() * resizeFactor;
float newLeft = outer.left + (outer.width() - newWidth) / 2f;
float newTop = outer.top + (outer.height() - newHeight) / 2f;
return new RectF(newLeft, newTop, newWidth + newLeft, newHeight + newTop);
}

Here's how I did it. (This algorithm works great with images.)
Let's say you have a rectangle, and a container (also a rectangle):
aspectRatio = screen.width / screen.height
if (rectangle.width >= rectangle.height)
{
resizeFactor = container.width / rectangle.width
rectangle.width = rectangle.width * resizeFactor
rectangle.height = rectangle.height * resizeFactor * aspectRatio
}
else
{
resizeFactor = container.height / rectangle.height
rectangle.width = rectangle.width * resizeFactor / aspectRatio
rectangle.height = rectangle.height * resizeFactor
}
You can optimise this algorithm a little bit by changing line 6 to:
rectangle.width = container.width
and the same to line 13 if you want to.

I just had to deal with something similar: let's call rectangle2 the image, and rectangle1 the window. So our task is to fit an image into the window.
Also in my scenario, both the image and the window have an "inner" coordinates system of [-1,1]X[-1,1]. In fact, inside the window, there is a viewport (rectangle3 in the question) where the image will be projected, and so our task is to find the largest viewport (a rectangle inside window) that has the same aspect ratio (width/height) as image.
The viewport is determined by a fraction of the width/height of the image:
viewport_width = scale_w * window_w
viewport_height = scale_h * window_h
So our goal is to find the right scaling of the width and height of window, such that the viewport will have the same aspect ratio (width/height) as the image:
# goal:
scale_w * window_w == image_w
scale_h * window_h == image_h
Which means that the scale we are looking for satisfies:
(scale_w / scale_h) == ((image_w / image_h) / (window_w / window_h))
Let's denote the right hand side of this equation by s for a moment.
Since we are looking for the maximal viewport, surely at least one out of scale_w and scale_h will be equal to 1. So - if s<1, meaning the window is wider than the image, we will have scale_w=s, scale_h=1 (this corresponds to the figure above, with scale_w=0.5). And if s>1, meaning the image is wider than the window, we will have scale_w=1, scale_h=1/s.
In python code it looks like this:
def compute_viewport_scale(image_size, window_size):
image_w, image_h = image_size
window_w, window_h = window_size
im_ratio, win_ratio = image_w / image_h, window_w / window_h
scale = im_ratio / win_ratio
if scale > 1: # image is wider than screen
scale_w = 1
scale_h = 1 / scale
else: # window is wider then image
scale_w = scale
scale_h = 1
viewport_w, viewport_h = window_w * scale_w, window_h * scale_h
assert (round(viewport_w / viewport_h, 5) == round(image_w / image_h, 5))
return scale_w, scale_h
Since I wanted to be absolutely sure I have all the cases covered, I constructed a list of examples for sizes, to make sure indeed this formula is correct. And indeed, if you run all these lines, you will get no assert errors :)
# aspect ratio = 1
compute_viewport_scale((100, 100), (100, 100))
compute_viewport_scale((100, 100), (200, 200))
compute_viewport_scale((200, 200), (100, 100))
# image is wider than screen: (im_w / im_h) > (win_w / win_h) [ i.e. im_ratio > win_ratio ]
# (im_ratio < 1 and win_ratio > 1 cant happen)
# im_w > win_w and im_h > win_h
compute_viewport_scale((300, 200), (100, 90)) # im_ratio > 1 and win_ratio > 1
compute_viewport_scale((200, 300), (100, 200)) # im_ratio < 1 and win_ratio < 1
compute_viewport_scale((300, 200), (100, 150)) # im_ratio > 1 and win_ratio < 1
# im_w > win_w and im_h < win_h
compute_viewport_scale((150, 50), (110, 100)) # im_ratio > 1 and win_ratio > 1
compute_viewport_scale((200, 300), (100, 400)) # im_ratio < 1 and win_ratio < 1
compute_viewport_scale((300, 100), (100, 150)) # im_ratio > 1 and win_ratio < 1
# (im_w < win_w and im_h > win_h cant happen)
# im_w < win_w and im_h < win_h
compute_viewport_scale((150, 50), (200, 100)) # im_ratio > 1 and win_ratio > 1
compute_viewport_scale((90, 100), (100, 200)) # im_ratio < 1 and win_ratio < 1
compute_viewport_scale((110, 100), (100, 200)) # im_ratio > 1 and win_ratio < 1
# image is wider than screen: (im_w / im_h) < (win_w / win_h) [ i.e. im_ratio < win_ratio ]
# (im_ratio > 1 and win_ratio < 1 cant happen)
# im_w > win_w and im_h > win_h
compute_viewport_scale((300, 200), (100, 50)) # im_ratio > 1 and win_ratio > 1
compute_viewport_scale((200, 400), (100, 150)) # im_ratio < 1 and win_ratio < 1
compute_viewport_scale((200, 400), (150, 100)) # im_ratio < 1 and win_ratio > 1
# (im_w > win_w and im_h < win_h cant happen)
# im_w < win_w and im_h > win_h
compute_viewport_scale((150, 100), (200, 50)) # im_ratio > 1 and win_ratio > 1
compute_viewport_scale((200, 400), (380, 390)) # im_ratio < 1 and win_ratio < 1
compute_viewport_scale((200, 400), (390, 380)) # im_ratio < 1 and win_ratio > 1
# im_w < win_w and im_h < win_h
compute_viewport_scale((300, 200), (600, 300)) # im_ratio > 1 and win_ratio > 1
compute_viewport_scale((200, 300), (500, 600)) # im_ratio < 1 and win_ratio < 1
compute_viewport_scale((50, 100), (300, 200)) # im_ratio < 1 and win_ratio > 1

Related

How to proportionally elongate image down center of camera

So I currently have a screen with a width of 1080 pixels. I also have a camera, centered in the middle of the screen. The camera gives me the position on a screen as a value between 0 and 1. This way, when the width of the screen is multiplied by the position, you get its placement in the screen window. See below:
so the old is
0 , .5, 1 <-- The position in the screen (X)
0, 540, 1080 <-- The screen size (Y)
X*Y = Pixel Placement
However, I would like to crop off some of the camera so it corresponds more accurately to my screen. The new is:
.25, .5, .75 <-- (X)
0, 540, 1080 <-- (Y)
What is the equation for calculating this placement? I feel like I've learned this but forgotten the method. Thanks.
You have linear relationship
Y = Yleft + (X - Xleft) * (Yright - Yleft) / (Xright - Xleft)
For your example values
Yleft = 0
Yright = 1080
Xleft = 0.25
Xright = 0.75
so
Y = (X - 0.25) * 1080 / 0.5 = 2160 * (X - 0.25)
for X = 0.5
Y = 2160 * (0.5-0.25) = 540

How to calculate an orbit around the earth with respect to Mercator projection?

I'm trying to create a small interactive UFO-flying-around-the-earth scene in three.js.
I thought it would be good to control the UFO on a 2D projection of the map (like a minimap), convert the pixel coordinates to lat/lng coordinates and finally transform lat/lng to a Vector that I can use for my 3D scene.
As it turns out, it wasn't.
Works great if the UFO flies around the Equator (+/- some degrees), but for sure I forgot to apply the projection to my 2D controls:
Now I'm a little bit lost. My controls (WASD keys) basically sets the speed and the rotation of the UFO on the minimap, but I have to add some sort of "correction" to the player. That's how the values are set at the moment:
let left = parseFloat(this.minimapPlayer.style.left) + this.speed * Math.cos(Math.PI / 180 * this.rotation)
let top = parseFloat(this.minimapPlayer.style.top) + this.speed * Math.sin(Math.PI / 180 * this.rotation)
Is there a way to create a "real" orbit, so that my UFO doesn't always fly through both poles? Or may there be a better approach to handle the orbit of the UFO (maybe without the minimap)?
Here's the full animation code so far:
animatePlane(firstRender) {
requestAnimationFrame(this.animatePlane)
// set next position
let left = parseFloat(this.minimapPlayer.style.left) + this.speed * Math.cos(Math.PI / 180 * this.rotation)
let top = parseFloat(this.minimapPlayer.style.top) + this.speed * Math.sin(Math.PI / 180 * this.rotation)
// handle border collisions
if (left < 0) {
left = this.minimapBounds.width
}
if (left > this.minimapBounds.width) {
left = 0
}
if (top < 0 || top > this.minimapBounds.height) {
this.rotation = this.rotation * -1
if (this.rotation > 0) {
this.plane.up.set(0, 1, 0)
} else {
this.plane.up.set(0, -1, 0)
}
top = top + this.speed * Math.sin(Math.PI / 180 * this.rotation)
if (left < this.minimapBounds.width / 2) {
left = left + this.speed * Math.cos(Math.PI / 180 * this.rotation) + this.minimapBounds.width / 2
} else {
left = left + this.speed * Math.cos(Math.PI / 180 * this.rotation) - this.minimapBounds.width / 2
}
}
this.minimapPlayer.style.left = `${left}px`
this.minimapPlayer.style.top = `${top}px`
// convert to lat/lng
const lat = (180 / this.minimapBounds.height) * (top - this.minimapBounds.height / 2) * -1
const lng = (360 / this.minimapBounds.width) * (left - this.minimapBounds.width / 2)
// convert to vector
const p = this.latLongToVector3(lat, lng, this.radius, 200)
this.plane.position.set(p.x, p.y, p.z)
// bottom of the plane should always look the the middle of the earth
this.plane.lookAt(new THREE.Vector3(0,0,0))
}

Zooming/scaling a tiled image anchoring the zoom point to the mouse cursor

I've got a project where I'm designing an image viewer for tiled images. Every image tile is 256x256 pixels. For each level of scaling, I'm increasing the size of each image by 5%. I represent the placement of the tiles by dividing the screen into tiles the same size as each image. An offset is used to precicely place each image where needed. When the scaling reaches a certain point(1.5), I switch over to a new layer of images that altogether has a greater resolution than the previous images. The zooming method itself looks like this:
def zoomer(self, mouse_pos, zoom_in): #(tuple, bool)
x, y = mouse_pos
x_tile, y_tile = x / self.tile_size, y / self.tile_size
old_scale = self.scale
if self.scale > 0.75 and self.scale < 1.5:
if zoom_in:
self.scale += SCALE_STEP # SCALE_STEP = 5% = 0.05
ratio = (SCALE_STEP + 1)
else:
self.scale -= SCALE_STEP
ratio = 1 / (SCALE_STEP + 1)
else:
if zoom_in:
self.zoom += 1
self.scale = 0.8
ratio = (SCALE_STEP + 1)
else:
self.zoom -= 1
self.scale = 1.45
ratio = 1 / (SCALE_STEP + 1)
# Results in x/y lengths of the relevant full image
x_len = self.size_list[self.levels][0] / self.power()
y_len = self.size_list[self.levels][1] / self.power()
# Removing extra pixel if present
x_len = x_len - (x_len % 2)
y_len = y_len - (y_len % 2)
# The tile's picture coordinates
tile_x = self.origo_tile[0] + x_tile
tile_y = self.origo_tile[1] + y_tile
# The mouse's picture pixel address
x_pic_pos = (tile_x * self.tile_size) -
self.img_x_offset + (x % self.tile_size)
y_pic_pos = (tile_y * self.tile_size) -
self.img_y_offset + (y % self.tile_size)
# Mouse percentile placement within the image
mouse_x_percent = (x_pic_pos / old_scale) / x_len
mouse_y_percent = (y_pic_pos / old_scale) / y_len
# The mouse's new picture pixel address
new_x = (x_len * self.scale) * mouse_x_percent
new_y = (y_len * self.scale) * mouse_y_percent
# Scaling tile size
self.tile_size = int(TILE_SIZE * self.scale)
# New mouse screen tile position
new_mouse_x_tile = x / self.tile_size
new_mouse_y_tile = y / self.tile_size
# The mouse's new tile address
new_tile_x = new_x / self.tile_size
new_tile_y = new_y / self.tile_size
# New tile offsets
self.img_x_offset = (x % self.tile_size) - int(new_x % self.tile_size)
self.img_y_offset = (y % self.tile_size) - int(new_y % self.tile_size)
# New origo tile
self.origo_tile = (int(new_tile_x) - new_mouse_x_tile,
int(new_tile_y) - new_mouse_y_tile)
Now, the issue arising from this is that the mouse_.._percent variables never seem to match up with the real position. For testing purposes, I feed the method with a mouse position centered in the middle of the screen and the picture centered in the middle too. As such, the resulting mouse_.._percent variable should, in a perfect world, always equal 50%. For the first level, it does, but quickly wanders off when scaling. By the time I reach the first zoom breakpoint (self.scale == 1.5), the position has drifted to x = 48%, y = 42%.
The self.origo_tile is a tuple containing the x/y coordinate for the tile to be drawn on screen tile (0, 0)
I've been staring at this for hours, but can't seen to find a remedy for it...
How the program works:
I apologize that I didn't have enough time to apply this to your code, but I wrote the following zooming simulator. The program allows you to zoom the same "image" multiple times, and it outputs the point of the image that would appear in the center of the screen, along with how much of the image is being shown.
The code:
from __future__ import division #double underscores, defense against the sinister integer division
width=256 #original image size
height=256
posx=128 #original display center, relative to the image
posy=128
while 1:
print "Display width: ", width
print "Display height: ", height
print "Center X: ", posx
print "Center Y: ", posy
anchx = int(raw_input("Anchor X: "))
anchy = int(raw_input("Anchor Y: "))
zmag = int(raw_input("Zoom Percent (0-inf): "))
zmag /= 100 #convert from percent to decimal
zmag = 1/zmag
width *= zmag
height *= zmag
posx = ((anchx-posx)*zmag)+posx
posy = ((anchy-posy)*zmag)+posy
Sample output:
If this program outputs the following:
Display width: 32.0
Display height: 32.0
Center X: 72.0
Center Y: 72.0
Explanation:
This means the zoomed-in screen shows only a part of the image, that part being 32x32 pixels, and the center of that part being at the coordinates (72,72). This means on both axes it is displaying pixels 56 - 88 of the image in this specific example.
Solution/Conclusion:
Play around with that program a bit, and see if you can implement it into your own code. Keep in mind that different programs move the Center X and Y differently, change the program I gave if you do not like how it works already (though you probably will, it's a common way of doing it). Happy Coding!

What is the rotation that gives an horizontal flip and a vertical flip?

Given this unit circle (degrees):
What would be the code for functions that would rotate the image to flip it horizontally and vertically?
For example,
FlipHorizontal(315) = 225,
FlipHorizontal(45) = 135,
FlipVertical(315) = 45,
FlipVertical(135) = 225.
add 180, modulo 360. so add 180 and subtract 360 if it's greater than 360.
angle += 180;
if (angle > 360) angle -= 360;
for example:
if you want to FlipHorizontal(315) = 225, you need to do something like this:
1) alpha > Pi?
2) if yes, you transformation will be 2Pi - alpha -> Pi + alpha, where 2Pi - alpha = 315.
3) if no, you transformation will be alpha -> Pi - alpha, where alpha = 45.
Solve this task like maths' task
vflip(a)
{
return 360-a;
}
hflip(a)
{
if (a > 180)
return 540 - a;
else
return 180 - a;
}
flipboth(a)
{
return 360 - ((a>180)?540 - a:180 - a);
//aka
//return vflip(hflip(a));
}

Optimally place a pie slice in a rectangle

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.

Resources