Given a square (described by x, y, width, height) and an angle (in radians) I need to calculate a vector that originates at the squares centre and terminates at the point that collides with the edge of the square at the given angle.
I'm really most interested in the point it collides at so if that would make calculation more efficient let me know.
Can this be generalized to Rectangles? How about polygons in general?
The vector will be center + (cos(angle), sin(angle))*magnitude
. Given that you want to intersect this with a square, you need to determine magnitude. You can get that with a square with:
float abs_cos_angle= fabs(cos(angle));
float abs_sin_angle= fabs(sin(angle));
if (width/2/abs_cos_angle <= height/2/abs_sin_angle)
{
magnitude= fabs(width/2/abs_cos_angle);
}
else
{
magnitude= height/2/abs_sin_angle;
}
However, cos(angle) or sin(angle) could be zero, so you should cross multiply that out to get:
float abs_cos_angle= fabs(cos(angle));
float abs_sin_angle= fabs(sin(angle));
if (width/2*abs_sin_angle <= height/2*abs_cos_angle)
{
magnitude= width/2/abs_cos_angle;
}
else
{
magnitude= height/2/abs_sin_angle;
}
And you can trivially get the end point from that.
EDIT: Here's a snippet you can drop in place to verify this works with the currently accepted answer:
double magnitude;
double abs_cos_angle= fabs(cos(angle));
double abs_sin_angle= fabs(sin(angle));
if (width/2*abs_sin_angle <= height/2*abs_cos_angle)
{
magnitude= width/2/abs_cos_angle;
}
else
{
magnitude= height/2/abs_sin_angle;
}
double check_x= x + cos(angle)*magnitude;
double check_y= y + sin(angle)*magnitude;
printf(" a = %d deg: x = %lf; y = %lf\n",(int)(angle/pi*180),check_x,check_y);
Clearly this is applies to an axis aligned rectangle. You can do something similar by finding the closest intersection between the testing vector and every edge in a polygon. (You can optimize that further, but that's left as an exercise to the reader.)
EDIT: the correct solution for rectangles. It even does not crash if width or height are zeros!
Language: C++.
tan(89.99999) thanks to James Fassett for testing my code.
#include <cstdio>
#include <math.h>
// declare nonstandard signum function
double sign(double x);
//define pi because I forgot where it's declared
double const pi = 3.14159;
//declare non-standard contangent function
double cot(double x);
int main()
{
for (double angle = 0.0 ; angle<2*pi; angle += 0.1){
//angle should be within [0, 2*pi) range
//x and y point to the _middle_ of the rectangle
double x = 0; double y = 0 ;
double width = 1, height = 4;
double base_angle = atan(height/width);
// the angle between rectangle diagonal and Ox axis
double px,py;
// Which side we're on?
bool left = (fabs(angle - pi) < base_angle);
bool right = (angle> 2*pi-base_angle || angle < base_angle);
bool top = (fabs(angle - pi/2) <= fabs(pi/2 - base_angle));
bool bottom = (fabs(angle - 3*pi/2) <= fabs(pi/2 - base_angle));
// The helper values used to adjust sides
int lr = (left?-1:0) + (right?1:0);
int tb = (bottom?-1:0) + (top?1:0);
if (lr) {
// we're on vertical edge of rectangle
px = x+width/2*lr;
py = y+width/2*tan(angle)*lr;
} else {
// we're on the horizontal edge or in the corner
px = x+height/2*cot(angle)*tb;
py = y+height/2*tb;
}
printf(" a = %d deg: x = %lf; y = %lf\n",(int)(angle/pi*180),px,py);
}
return 0;
}
// define nonstandard signum function
double sign(double x)
{
if (x<0) return -1;
if (x>0) return 1;
return 0;
}
//define non-standard contangent function
double cot(double x)
{
return tan(pi/2 - x);
}
Given the square's width and height you can then determine the center of the square (x+.5w, y+.5h).
From there, you can use some trigonometry to determine the length of the vector line:
tan(angle) = 0.5x / a
where a = the distance between the center of the square and the edge of the square. Your points are then x = a, y = (height).
Please be gentle, as it has been some time since I've used a lot of this math! :-)
Edit: There is another working implementation from Pavel now (good dedication from him to put in effort debugging his solution) but I'll leave this here as another alternative that works only for squares (Pavel's works for Rectangles).
private function calculatePointOnSquare(width:Number, angle:Number):Point
{
// simple angle wrapping
angle = (Math.PI*2 + angle) % (Math.PI*2);
// calculate a normalized vector from the centre
// of a square to the edge taking into account
// the eight possible quadrants
var myX:Number;
var myY:Number;
if(angle < Math.PI/4)
{
myX = 1;
myY = Math.tan(angle);
}
else if(angle < Math.PI/2)
{
myX = Math.tan(Math.PI/2 - angle);
myY = 1;
}
else if(angle < 3*Math.PI/4)
{
myX = -Math.tan(angle - Math.PI/2);
myY = 1;
}
else if(angle < Math.PI)
{
myX = -1;
myY = Math.tan(Math.PI - angle);
}
else if(angle < 5*Math.PI/4)
{
myX = -1;
myY = -Math.tan(angle - Math.PI);
}
else if(angle < 3*Math.PI/2)
{
myX = -Math.tan((3*Math.PI/2) - angle);
myY = -1;
}
else if(angle < 7*Math.PI/4)
{
myX = Math.tan(angle - (3*Math.PI/2));
myY = -1;
}
else
{
myX = 1;
myY = -Math.tan(Math.PI*2 - angle);
}
// scale and translate the vector
return new Point(
(myX * width/2) + width/2,
(myY * width/2) + width/2);
}
Generalized to rectangles, if a = the angle of vector from the horizontal increasing counter cloclkwise, then the points coordinates can be calculated by the following:
let dx = distance from center horizontally, and
dy = distance form the center vertically, then
dx = if (tan(a) == 0, then width/2, else Min( height / (2 * tan(a)), width/2)
dy = if ABS(a) == Pi/2 then height/2 else Min( (width/2) * tan(a)), height/2)
Then coordinates of the point are:
px = (x+width/2) + dx for right quadrants (Pi/2 >= a >= - Pi/2);
= (x+width/2) - dx for left quadrants (Pi/2 <= a <= 3Pi/2)
py = (y+height/2) + dy for lower quadrants (Pi <= a <= 2Pi);
= (y+height/2) - dy for upper quadrants (0 <= a <= Pi);