tags:

views:

596

answers:

5

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?

A: 

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);
Charles Bretana
In most languages, the tan(angle) will blow everything up if angle is +/- pi/2 radians regardless of whether it's inside the Min() or not.
Pillsy
This does not work for all values in the range [0, 2*pi]. For example when a=6.201503898186251 then dx = -305.38621689379323 which is clearly incorrect. Altering the calculations of dx to include a Max(-width/2) bounds the values correctly but the answer remains incorrect within many ranges including [0, pi/2]
James Fassett
A: 

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! :-)

Josh E
The hard bit is determinging a -- the distance from the centre to the edge of the rectangle. If I could do that then sohcahtoa is all I'd need.
James Fassett
The above equation, when solved, provides that. You have the angle (and thus the tangent), and you have x. all you need is to solve for a, right?
Josh E
Sorry Josh - I just don't know how to go from what you've written to a testable code solution.
James Fassett
No problem - I thought from the tags that you were looking for a straight-mathematical type solution. If I get some time later today I'll see if I can whip something up for ya
Josh E
Thanks for the response. There are 2 working answers already and I have accepted one (MSN's) as the correct one. I appreciate your intention to look at it again but I wouldn't do so unless you feel you could better one of the existing correct answers. Cheers!
James Fassett
+2  A: 

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);
}
Pavel Shved
Sorry Pavel, but is "sign" meant to be "sine"? Is cot cosine or cotangent? When I make those assumptions not all calculated points lie on the perimeter of the rectangle.
James Fassett
oh, _sign_ means "sign", or "signum". +1 for positive numbers, -1 for negative ones, and 0 for the zero.
Pavel Shved
_cot_ means contangent. Check wikipedia for the way these functions are denoted in English. [[offtopic] And re-check all the school math, by the way: for example "right triangle" was a surprise for me.]
Pavel Shved
I've tested this and while correct for some ranges of angle it is wrong for the majority. See my answer which is much more verbose but correct.
James Fassett
I fixed my post. It contained a couple of typos (width wasn't multiplied by 2 and I forgot about the left side of the rectangle), but there were no errors in mathematics! Please, mark my solution with green color and feel free to use the code without fee :)
Pavel Shved
I'm afraid it is still incorrect although closer. Between values [3*pi/4, pi] for instance it incorrectly calculates the y value. If you port my version to C (should be a breeze) you can check your results for squares which may help.
James Fassett
Oh-hoh. Mommy told me that I shouldn't do computational geometry after 3 AM... Code fixed.
Pavel Shved
Cool - I can verify this works for at least squares. Thanks!
James Fassett
Sorry Pavel - I have to accept MSN's answer here. It is more direct, calculates the correct values and is more efficient. Thanks for the answer!
James Fassett
wht you just done is persuading i should never help solving computational geometry out there . go t ohell.
Pavel Shved
@Pavel: your solution is correct however MSN's solution is *better*. I'm sorry if that hurts your feelings but it is a fact. I appreciate very much the help that you gave me and the time you took to get to a working solution. I hope you continue to help others who are in need.
James Fassett
yes it is. totally better an I accept it. But that doesn't change the fact that in computational geometry there's always a better solution, because all sines and cosines are inerconnected. And your endeavor merely makes me sure that it's not worth solving.
Pavel Shved
+3  A: 

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.)

MSN
I think the condition should be "if (width*fabs(sin(angle))/2 <= height*fabs(cos(angle))/2). Otherwise this is a nice solution.
Accipitridae
I have tested this answer between [0, pi*2] and it does not give correct values.
James Fassett
I've done more testing to be precise in my debugging of your solution. You will notice the values calculated for 0 are the same as for pi even though they are meant to be on the opposite side of the rectangle. This is because your solution doesn't take the direction of the magnitude into account. See Pavels solution for how he handles that (in mine I use that large if/elseif block because of my knoweldge of the angles at the corners of squares but Pavels solution is general to rectangles)
James Fassett
The idea is right. It's just carelessly implemented. It is also necessary to take the absolute value of magnitude. The currently accepted answer is just horrible.
Accipitridae
Just to be clear, which answer is horrible? (I realize mine was a bit careless...)
MSN
Works with your changes now - I agree this is a simpler and more elegant approach than the previous accepted answer and now that it works correctly I have elected it as the accepted answer. Can you provide a reference that explains it (just so I can benefit from the understanding of the math as well as the code)?
James Fassett
I should also note that a perfectly correct solution would be "double check_x = (x+width/2) + cos(angle)*magnitude; double check_y = (y+height/2) + sin(angle)*magnitude".
James Fassett
Oh, really? So it's not a correct solution either, but you still have marked it as corect? Well, good for you, pal.
Pavel Shved
@Pavel: the solution *is* correct if one considers x and y the centre of the rectangle. In my problem statement x and y represent the top left corner of the rectangle so the centre of the rectangle is actually x+width/2, y+width/2.
James Fassett
cool. whatever. not going to discuss anything with you actually.
Pavel Shved
@Pavel, in your solution, x and y were the center of the rectangle. So I used that convention in my little function at the end. Anyways, it's not really that important, since you can always offset the calculations and still end up with the right result.
MSN
@MSN: I used to be a guy who solved computational geometry on programming contests in our team. My experience says that you ALWAYS use the tan an cot functions instead of dividing sines and cosines. ALWAYS. My experience. Mine. Whatever, anyway. I got my rep anways, but just to be sure someone will never pick the wrong solution. You should never get sines and cosines divided in separate variables. Yes I said it again because it's inportant. Sorry.
Pavel Shved
@Pavel, if you are objecting to my use of sin and cos as individual function calls, you can either get around that with invoking a fsincos instruction directly (at least on x86) or recasting each in terms of tan or cot. I used them for the sake of clarity. I wouldn't actually do this in real life, since it's much easier just to calculate the intersection with an edge of a general polygon with a unit vector times some scale that you are determining from the center (or from any location for that matter).
MSN
A: 

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);
}
James Fassett
You realize that my solution is both general and does not have degeneracies, right? (And is tremendously simpler)
MSN
MSN - your solution is incorrect when tested.
James Fassett
Solution fixed.
MSN