Finding points on a rectangle at a given angle
Javascript version:
function edgeOfView(rect, deg) {
var twoPI = Math.PI*2;
var theta = deg * Math.PI / 180;
while (theta < -Math.PI) {
theta += twoPI;
}
while (theta > Math.PI) {
theta -= twoPI;
}
var rectAtan = Math.atan2(rect.height, rect.width);
var tanTheta = Math.tan(theta);
var region;
if ((theta > -rectAtan) && (theta <= rectAtan)) {
region = 1;
} else if ((theta > rectAtan) && (theta <= (Math.PI - rectAtan))) {
region = 2;
} else if ((theta > (Math.PI - rectAtan)) || (theta <= -(Math.PI - rectAtan))) {
region = 3;
} else {
region = 4;
}
var edgePoint = {x: rect.width/2, y: rect.height/2};
var xFactor = 1;
var yFactor = 1;
switch (region) {
case 1: yFactor = -1; break;
case 2: yFactor = -1; break;
case 3: xFactor = -1; break;
case 4: xFactor = -1; break;
}
if ((region === 1) || (region === 3)) {
edgePoint.x += xFactor * (rect.width / 2.); // "Z0"
edgePoint.y += yFactor * (rect.width / 2.) * tanTheta;
} else {
edgePoint.x += xFactor * (rect.height / (2. * tanTheta)); // "Z1"
edgePoint.y += yFactor * (rect.height / 2.);
}
return edgePoint;
};
Following your picture, I'm going to assume that the rectangle is centered at (0,0), and that the upper right corner is (w,h). Then the line connecting (0,0) to (w,h) forms an angle φ with the X axis, where tan(φ) = h/w.
Assuming that θ > φ, we are looking for the point (x,y) where the line that you have drawn intersects the top edge of the rectangle. Then y/x = tan(θ). We know that y=h so, solving for x, we get x = h/tan(θ).
If θ < φ, the line intersects with the right edge of the rectangle at (x,y). This time, we know that x=w, so y = tan(θ)*w.
Let's call a and b your rectangle sides, and (x0,y0) the coordinates of your rectangle center.
You have four regions to consider:
Region from to Where ==================================================================== 1 -arctan(b/a) +arctan(b/a) Right green triangle 2 +arctan(b/a) π-arctan(b/a) Upper yellow triangle 3 π-arctan(b/a) π+arctan(b/a) Left green triangle 4 π+arctan(b/a) -arctan(b/a) Lower yellow triangle
With a little of trigonometry-fu, we can get the coordinates for your desired intersection in each region.
So Z0 is the expression for the intersection point for regions 1 and 3
And Z1 is the expression for the intersection point for regions 2 and 4
The desired lines pass from (X0,Y0) to Z0 or Z1 depending the region. So remembering that Tan(φ)=Sin(φ)/Cos(φ)
Lines in regions Start End ====================================================================== 1 and 3 (X0,Y0) (X0 + a/2 , (a/2 * Tan(φ))+ Y0 2 and 4 (X0,Y0) (X0 + b/(2* Tan(φ)) , b/2 + Y0)
Just be aware of the signs of Tan(φ) in each quadrant, and that the angle is always measured from THE POSITIVE x axis ANTICLOCKWISE.
HTH!
Ok, whew!, I finally got this one.
NOTE: I based this off of belisarius's awesome answer. If you like this, please like his, too. All I did was turn what he said into code.
Here's what it looks like in Objective-C. It should be simple enough to convert to whatever your favorite language is.
+ (CGPoint) edgeOfView: (UIView*) view atAngle: (float) theta
{
// Move theta to range -M_PI .. M_PI
const double twoPI = M_PI * 2.;
while (theta < -M_PI)
{
theta += twoPI;
}
while (theta > M_PI)
{
theta -= twoPI;
}
// find edge ofview
// Ref: http://stackoverflow.com/questions/4061576/finding-points-on-a-rectangle-at-a-given-angle
float aa = view.bounds.size.width; // "a" in the diagram
float bb = view.bounds.size.height; // "b"
// Find our region (diagram)
float rectAtan = atan2f(bb, aa);
float tanTheta = tan(theta);
int region;
if ((theta > -rectAtan)
&& (theta <= rectAtan) )
{
region = 1;
}
else if ((theta > rectAtan)
&& (theta <= (M_PI - rectAtan)) )
{
region = 2;
}
else if ((theta > (M_PI - rectAtan))
|| (theta <= -(M_PI - rectAtan)) )
{
region = 3;
}
else
{
region = 4;
}
CGPoint edgePoint = view.center;
float xFactor = 1;
float yFactor = 1;
switch (region)
{
case 1: yFactor = -1; break;
case 2: yFactor = -1; break;
case 3: xFactor = -1; break;
case 4: xFactor = -1; break;
}
if ((region == 1)
|| (region == 3) )
{
edgePoint.x += xFactor * (aa / 2.); // "Z0"
edgePoint.y += yFactor * (aa / 2.) * tanTheta;
}
else // region 2 or 4
{
edgePoint.x += xFactor * (bb / (2. * tanTheta)); // "Z1"
edgePoint.y += yFactor * (bb / 2.);
}
return edgePoint;
}
In addition, here's a little test-view I created to verify that it works. Create this view and put it somewhere, it will make another little view scoot around the edge.
@interface DebugEdgeView()
{
int degrees;
UIView *dotView;
NSTimer *timer;
}
@end
@implementation DebugEdgeView
- (void) dealloc
{
[timer invalidate];
}
- (id) initWithFrame: (CGRect) frame
{
self = [super initWithFrame: frame];
if (self)
{
self.backgroundColor = [[UIColor magentaColor] colorWithAlphaComponent: 0.25];
degrees = 0;
self.clipsToBounds = NO;
// create subview dot
CGRect dotRect = CGRectMake(frame.size.width / 2., frame.size.height / 2., 20, 20);
dotView = [[DotView alloc] initWithFrame: dotRect];
dotView.backgroundColor = [UIColor magentaColor];
[self addSubview: dotView];
// move it around our edges
timer = [NSTimer scheduledTimerWithTimeInterval: (5. / 360.)
target: self
selector: @selector(timerFired:)
userInfo: nil
repeats: YES];
}
return self;
}
- (void) timerFired: (NSTimer*) timer
{
float radians = ++degrees * M_PI / 180.;
if (degrees > 360)
{
degrees -= 360;
}
dispatch_async(dispatch_get_main_queue(), ^{
CGPoint edgePoint = [MFUtils edgeOfView: self atAngle: radians];
edgePoint.x += (self.bounds.size.width / 2.) - self.center.x;
edgePoint.y += (self.bounds.size.height / 2.) - self.center.y;
dotView.center = edgePoint;
});
}
@end