Geometery/Trig Help!

BlitzMax Forums/BlitzMax Programming/Geometery/Trig Help!

daaan(Posted 2006) [#1]
Is there a way to calculate a point on the perimeter of a square/rect based on a given angle?

I can do it with a circle:
pointX = cos( angle )
pointY = sin( angle )

But I can't find a way to do the same thing for a square/rect.

Any ideas?


Scott Shaver(Posted 2006) [#2]
this is off the top of my head and I'm really tired so it might not work but: Make a right triangle from two corners of the rectangle and the third point is determined by the angle of the non-90 degree angle.

look at this:

http://www.btinternet.com/~se16/hgb/triangle.htm

it's for areas but you get the idea from the diagram and my description above hopefully.


SculptureOfSoul(Posted 2006) [#3]
I've thought up a hack method that will work, although I don't foresee it being particularly efficient. If Scott's method doesn't work let me know and I can whip up some pictures to better explain the method.

I've never had to actually do this in a game or something and haven't researched it, so I'm pretty sure the method I thought up is far less efficient than it could be. That's why I'm not describing it here yet ;).


daaan(Posted 2006) [#4]
@SculptureOfSoul - I'm currently working on Scotts idea but I welcome all suggestions because yours might be easier :)!


SculptureOfSoul(Posted 2006) [#5]
Alright, you twisted my arm ;). I'll get working on the pics and explanation. Keep in mind I'm no math whiz and I'm not sure that this method will work, but it seems to make sense (to me at least :).


SculptureOfSoul(Posted 2006) [#6]
Okay, first things first, we calculate the centerpoint of the rect. Store that away in a variable.

I'll be using the following rectangle as an example.



Now, we need to calculate the angle from the centerpoint up to the upper right corner.



We can calculate this angle using inverse tangent. First we need to get the tangent though.

The tangent of an angle is the opposite side divided by the adjacent side. So we need to calculate those two sides. This is easy since we know that the Y component will be exactly half of the height of the rectangle, and the x component will be exactly half of the width. Now, the opposite side of the angle is the y component, or 1. The adjacent side is the x component: 2. So opposite/adjacent = 1/2 = .5

.5 is the tangent of the unknown angle. Doing an inverse tangent, or Atan of that value will give us the actual angle. So the angle is 26.56505... as the next picture shows.



Now, any angle between 0 and Angle A will have an X value of CenterPoint_X + 1/2 width. In fact, if you think about it any angle between 360 and (360-a) will also have the same X value. Here's a picture to help visualize that



The X value is always the same for any angle between 360-a and a. The Y component of the point is the only thing that changes. So, we can treat any angle between 360-a and a as being in it's own "quadrant" and handle it from there. To do this in code we first need to calculate angle a as described above. Stash this away in a variable.

So now we can do a simple test we we want to find a point on the perimeter.

First I'm going to do a mod on the angle to make sure it falls between 0-360 degrees. This way, an angle of 720 is treated as 360, an angle of 540 is treated as 180, etc.

so in semi pseudo-code it might look something like this
angle_a 'this is the angle we calculated above
test_angle ' this is the angle we are finding the point for

test_angle = test_angle mod 360.

if ( (test_angle > 360 -  angle_a ) OR (test_angle < angle_a))

'calculate point here

endif


But how do we actually calculate the X and Y of the point? The X is easy. Remember, if the angle we are testing falls into this "quadrant" (360-a to a) we know the X will be Centerpoint_X + width/2. We need to figure out the Y.

To do so, we need to do some math again. So, let's say that we have a test_angle of 13 degrees. The following picture display how we can determine Y.



This time we know the angle, so we know that Tangent(13) = opposite/adjacent. So tangent(13) = y/2.
This leaves us with .2308 = y/2. Solving for y gives us y = .2308 * 2. Y = .4617.

However, .4617 is not the actual value of the Y component of the point. .4617 is the distance from the centerpoint Y to the actual points Y component. So to get the actual points Y component we subtract .4617 from the centerpoints Y. So we end up with
centerpoint_y = 1

1-.4617 = .5383

So the actual points values are
X = centerpoint_x + width/2
X = 2 + 2
X = 4

and

Y = centerpoint_y - y_component
Y = 1 - .4617
Y = .5383

To prove that this works on angles falling between (360-a) and 360, lets look at another example.



So doing the math on this one (which is all done in the picture above) we get a Y component of -.89045. Remember, we need to subtract this amount from the y of the centerpoint to get our actual Y value. So we get
X = 4
Y = 1 - (-.89045) 'negatives cancel out
Y = 1.89045

As you can see, the point we got makes sense given the angle we were testing.

Let me know if this makes sense or if you don't need me to go on anymore. Basically, what is left to do is to calculate the X component of the point when the test angle is greater than angle A but less than 180 minus angle a, or when the test angle is greater than 180 + a and less than 360 - a.

A test angle that falls between 180-a and 180 + a is basically computed the same way as the points above, except it's X value is NOT going to be centerpoint_x + 1/2 width but instead will be centerpoint_x - 1/2 width. Basically, we calculate the Y component the same as we did before, but the X component is now the "other side of the rectangle."

Like I said, let me know if this makes sense and if I should elaboraet further. I don't want to oversimplify and at the same time don't want to be too vague. Oh, and I should get back to coding my game for the moment ;).

Hope this helps.


daaan(Posted 2006) [#7]
Wow! Way to go and put in maximum effort! It's late here so I'm tired but after a quick read through I feel that I understand your idea pretty well. Tomarrow I'll read it again and start coding!


SculptureOfSoul(Posted 2006) [#8]
Hopefully you'll find it easier to code than I found it to explain ;).

I might whip up a code example myself if I find the time. I'm not sure where I'd use it yet but y'never know when the need might arise.


sswift(Posted 2006) [#9]
X = cos( angle )
Y = sin( angle )

Select True

  Case ((Angle >= 270+45) AND (Angle < 45)) OR ((Angle >= 90+45) AND (Angle < 180+45))

     Y = Y * (1.0 / X)
     X = 1

     X = X * DistanceToSides
     Y = Y * DistanceToSides

     If Angle >= 90+45 Then X = X * -1

  Case ((Angle >= 45) AND (Angle < 90+45)) OR ((Angle >= 180+45) AND (Angle < 270+45))

     X = X * (1.0 / Y)
     Y = 1

     X = X * DistanceToTopBottom
     Y = Y * DistanceToTopBottom
     
     If Angle >= 180+45 Then Y = Y * -1

End Select



This code uses the angle to calculate which side of the square the line would intersect if it were to continue in that direction, and then assumes that the distance to the side or top/bottom is 1. Being 1, it sets one of the coordinates to 1, and multiplies the other by 1 divided by the first, which extends it to the point it should be at if the first coordinate had been 1 in the first place.

The above code will need to be adjusted if you want to allow negative angles or angles greater than 360, but the premise is the same.

Oh and the above assumes the center of the rect is at 0. You'll have to subtract the center of the rect position from the X and Y coordinates before doing it, and add it back after.

Basically instead of trig stuff with tangents and triangles, I pretended that the line was a vector or a normal and the math is simple once you look at it that way and you know about how to normalize vecotrs by dividing their coordinates by their length.

This setup would support the top being farther from the center than the bottom, and the same for the sides as well.


SculptureOfSoul(Posted 2006) [#10]
Basically instead of trig stuff with tangents and triangles, I pretended that the line was a vector or a normal and the math is simple once you look at it that way and you know about how to normalize vecotrs by dividing their coordinates by their length


I think I gotta learn me some more maths. You made that way too easy. :)


SculptureOfSoul(Posted 2006) [#11]
Swift, maybe I'm just misunderstanding but I think that code will fail in specific circumstances on anything but squares. For instance, imagine you have a rectangle like so


0,0 ------2,0
   |     |
   |     |
   |  .  |
   |     |
   |     |
0,10----- 10,10



okay, that's kind of ugly, but it works.

Now, testing your code w/ an angle of 45 degrees I get this.

X = cos 45
Y = sin 45
X = .707
Y = .707

X = X * 1/Y
X = .707 * 1/.707
X = 1
Y = 1

X = X * distance to top
Y = Y * distance to top

X = 1 * 5
Y = 1 * 5

So the final point is (5,5), which certainly can't be right.
Maybe I'm doing something wrong?


SculptureOfSoul(Posted 2006) [#12]
Er, the final point would be the point I calculated above (5,5) minus the center point (1, 5), so it'd be (4, 0). While that *is* where the line would intersect the top, in this case a 45 degree angle hits the side first.

I think if you took your code above but changed the condition tests to the following format

[code]
Case ((Angle >= 270+ angle_a) AND (Angle < angle_a)) OR ((Angle >= 90+angle_a) AND (Angle < 180+angle_a))
[code]

using the angle to corner method I detailed above, the code would work correctly.


sswift(Posted 2006) [#13]
You're right, it won't work as is, but your angle to corner thing should fix it.

Or you could maybe skip the angle calculation altogether, and do both case calculations, and pick the one where Y doesn't exceed the allowed values for Y.

In other words, using the sign of X and Y, detemrine which direction the line is going. Then calculate where the line intersects say, the top and right side lines. One of those intersection points will result in a line which is shorter than the other. Pick that one. You don't even need to square root the distances to compare them.