Thursday 2 August 2012

Basic Trigonometry in Coding C/C++

As I missed putting up a post for July, it is time I put up a post or two during August on this "semi-abandoned" blog. I have been busy with work, and though I have been doing the odd bit of coding, I have not had the time to compose a post for here. Nevertheless, here I am now.

Nothing special this time. Just a review of some vital 2D trigonometry. There is little doubt that there is something slightly amiss with me, as I wake up each day as a blank sheet with regard to what I did or learned the day before, I have the bad feature of forgetting even important things until I refresh my memory with some trigger or another (and several coffees and a Red Bull). This post will my C++ trigonometry trigger!

There will be no tags for this post to aid searching, as it is a bit of a personal rant. If you are someone other than me reading this, then you stumbled in on it. Don't worry, you may read it, if you like - just don't think of it as any sort of "lesson", please. There are many, much better, "learn trigonometry" pages out there.

I'll start with the easiest thing. How does the computer figure trig? Well, quite normally, in essence. If I plotted a point using sin( ) and cos( ), this is what the computer "thinks" of as angles (in radians, of course)...


...and to convert Radians to Degrees (and vice versa), the following formulas may be used...

Degrees = Radians x (180 / Pi)

Radians = Degrees x (Pi / 180)

Now it is a bit of a pain in the backside (if you are programming games that use maps) that, first; you must use radians instead of degrees, and second; your natural "north" (ie; 0.0) is pointing over to the right of the screen instead of up. However, no real hassle, as a little bit of coding can produce a neat header like this one of mine to circumvent these problems, but that's another story.

What are tan( ), atan( ), and atan2( )?
First, here are the docs;
tan
atan
atan2

They speak for themselves. I want to cover the basics here. Here is a right angled triangle...


Now, I always had some trouble learning things other people's way. All that blah-blah about a line off a circle and such did not (ever) explain to me what a tangent was.

A TANGENT is simply the length of one of the (right-angle) sides of the triangle divided by the other, basically ignoring the hypotenuse (c). It gives you (and is essentially) the ratio of the dividend to the divisor.

So, one tangent is;

28 / 46 = 0.608695
...the other is;
46 / 28 = 1.642857

...and that's what all the hullabaloo is all about. Think of it as a fraction, if you like (28/46 or 46/28), and that is basically it. Think "The Ratio of the Side of the Triangle which is the Numerator to the side Side of the Triangle which is the Denominator in your Fraction".

That ratio can recover the length of either of the sides if you know the other side's value.

28 / 0.608695 = 46
...and...
46 x 0.608695 = 28

Want to know the value of angle (A)? That is where ArcTangent comes in. Feed it the fraction...

Atan( 28 / 46 ) = 31.33º  or  0.546788 rads

...and you get the value of Angle (A). How about angle (B)? Feed it the other fraction.

Atan( 46 / 28 ) = 58.67º  or  1.024007 rads

The Tan() function can be used if you know either of the angles formed by the right angled side with the hypotenuse. It will give you the ratio of the side OPPOSITE the angle you feed it...

Tan(58.67º)  = 1.642772

That's angle (B), giving us the ratio of side (b), with a small rounding error :).

And if you know is the angle, let's say (A) and any of the sides, lets say (a), you may directly use the tangent of that angle and the known value to calculate the adjacent...

28 / Tan(31.33º) = 46

Yeah, because Tan(31.33º) is simply 0.608695. Looks familiar now? No worries. Now; want to see an example of going nowhere?

Atan( Tan( 31.33º ) ) = 31.33º

Heh! Heh! Heh! Heh!

But now I want to clarify a difference (C/C++) between atan( ) and atan2f( ). This snippet shows what happens when we attempt to get the angles of 8 points from an origin of zero, using both atan() and atan2f();

Pastebin_Sample_1_Link

So, let's have a look at that. Once the program is run it becomes immediately apparent that atan() and atan2f() seem to provide different answers for points 0, 6 and 7. Or do they really?


In reality, no. The difference is that atan's answer handles the angles as four quadrants (4 x 90º), alternating positive / negative twice, and atan2f as two semi-circles (2 x 180º), one positive and one negative. In short, if you used atan instead of atan2f, you would need to do some quadrant differentiation (explained later on in this post), something that becomes unnecessary with atan2f. So, the latter is more convenient for game programming.

In fact, some other trigonometric functions (like sine and cosine) will handle the whole 0.0 to 6.28318 radians, without resorting to a negative and a positive half arc. This is even more convenient, but you will need to - or should - correct the results of any atan2f return (if your program uses it) to conform. Fortunately, it is easy...

if(angle < 0.0f)
{
     angle += (M_PI * 2);
}

...and, essentially, you are away.

What are sin( ), cos( ), asin( ) and acos( )?
Again, the docs...
sin
cos
asin
acos

But first, a quick anecdote of the sort of stupidity I am capable of. In school, back in 1984, I hated trigonometry and never really understood it, as the teachers were equally as incapable of explaining it in a way I could relate to. Two years later I had my first computer. Suddenly mathematics was exciting, and I wanted to write my own games. Unfortunately, the only memory that remained of trigonometry was this thing of Pythagoras;

Hypotenuse = Adjacent² + Opposite²

So, I wanted to make a game where a ship sailed from port to port. I struggled and figured out this bit of code;

Pastebin_Sample_2_Link

Well, it wasn't quite THAT bit of code. It was in Spectrum BASIC, but the algorithm was the same. What I had done with the lines...

    float x_ratio = x_dist / slant_distance;
    float y_ratio = y_dist / slant_distance;

...was re-invent sine and cosine. A bit later on, about a week later, I thought to myself; 

"Wouldn't it be nice if there was a function that did that calculation?" 

And then I rediscovered (that is, partly recalled through the fog of forgetfulness in my head), ArcTangent, Sine, and Cosine. I believe this "rediscovery" of trigonometry is well in among the "Top Ten Most Joyous" moments of my existence. Here is the alternative code..









Enough nostalgia...

Here's my sample graphic;



So, the Sine and Cosine are simply the result of dividing the length of the opposite or adjacent - of angle (A) - by the hypotenuse. In the graphic, a triangle is formed inside the circle. The hypotenuse (c) is equivalent to the radius of the circle, 32 cm. On a 2D Cartesian graph, the center of the circle is at points (0,0). Point (pt), where the radius (hypotenuse) ends, the values on the x and y axes are, respectively 24.5 cm and 20.6 cm.

Dividing the opposite by the radius will give the ratio of the distance (pt) is along the y axis, in relation to that radius. This is the Sine.

20.6 / 32 = 0.64375




Dividing the adjacent by the radius will give the ratio of the distance (pt) is along the x axis, in relation to that radius. This is the Cosine.





24.5 / 32 = 0.76563


So, the Sine is related to the opposite, and the Cosine to the adjacent. That needs to be borne in mind, because the same applies for the ArcSine and ArcCosine, which will provide us the value of angle (A), much like ArcTangent did for Tangent.

If you are using the Sine value, utilize ArcSine, feeding it the fraction Opposite / Hypotenuse.

Asin( 20.6 / 32) = 40º  or in radians,  0.699 

...and if you are using Cosine value, utilize ArcCosine, feeding it the fraction Adjacent / Hypotenuse.

Acos( 24.5 / 32 ) = 40º  or in radians,  0.699

So, barring some rounding precision errors, using the Cos( ) and Sin( ) functions with angle (A) should produce much the same Sine and Cosine values as were calculated above.

Sin( 40º ) = 0.64412

Cos( 40º ) = 0.7660

So, how is that useful? Multiply each by the hypotenuse and you will get the displacements along the y and x axes that represent point (pt).

Y = Sin( 40º ) x 32 = 20.57

X = Cos( 40º ) x 32 = 24.51

So, shall I be silly now? If we don't know the length of the hypotenuse, but we do know the angle and the length of the opposite (y axis displacement), we can shuffle the formula around to calculate the hypotenuse...

20.57 / Sin( 40º ) = 32.0

And finally, how to go nowhere again....

Asin( Sin( 40º) ) = 40º

Acos( Cos( 40º ) ) = 40º

But wait, there's more! Get this now and you take away additional two items of trigonometry trivia, totally free, today;

Acos( Sin( 40º ) ) = 50º

Asin( Cos( 40º) ) = 50º

What? Why? Well, basically, both Sine and Cosine deal only with angles up to 90º, so obtaining an ArcCosine of a Sine or an ArcSine of a Cosine will give you the remainder of the angle to complete 90º.

But, hey, that raises a question, doesn't it? In game programming, we almost invariably have to deal with the whole 360 degrees (or better said, the whole Pi x 2), and not just 90º. No worries. The position of the point relative to its origin will fall in one of four quadrants, each of a 90º arc...



Most of the time, we will be feeding sin( ) and cos( ) radians in game programs. The problem can be solved by feeding them the radians as depicted in the very first figure of this post (two semi-circles). Notice that computer screens have your normal Cartesian coordinates reversed on the Y axis, counting up as the go down the screen. X is normal. The maths, however, is still standard. 

Some proof? Here is a runnable snippet of C/C++...


Pastebin_Sample_4_Link

The "angle" is negative, in radians. If the code is run, you will notice that the Y coordinate moves away from zero in the negative direction. If I plotted those coordinates on a screen, the point would be travelling diagonally UP and to the RIGHT.

Run it with; 

float angle = -2.35619f;

Where is the point going to go?

Or better still, feed it this...

float angle = 3.92699f;

...it's the same thing! Compare.


Why not? C++ handles it, now. I seem to remember some other languages (Spectrum BASIC, if I am not mistaken) did not, which required some serious quadrant differentiation to get things going the way you wanted them to go.

However, do be careful with asin() and acos(). They WILL return to the four quadrants format, and will require some interpretation by the code to get thing absolutely right. Try this;

float angle = asinf(sin(4.71237)); 
// That is, we are giving it 270 degrees (in radians 4.71237) and expecting it to give back
// the same 4.71237. But it gives us minus 1.57078, equivalent to minus 90 degrees...

Oops! We need to correct for the quadrants. The next two code samples will attempt to clarify this "problem"...

WITHOUT correction (plain straight asin)...

Pastebin_Sample_5_Link

And now, WITH quadrant correction...



Pastebin_Sample_6_Link



The above snippet converts the asin() tendency to treat the full circle as four quadrants of 90º into a full 360º positive degrees (or as it really is, 0.0 to 6.28318 radians). Here's a figure to further clarify...




Note that acos() treats the circle much as asin() does, generally, and requires a similar correction itself, but there is no reason to go into that right here, as it is much the same sort of thing, and not difficult to figure out once the above is understood. 

So, that wraps that up. Now, onto something else; the Cosine Rule.

Cosine Rule
There are no C/C++ functions for this. The Cosine Rule is a very real world sort of application, where you might be able to measure the length of the three sides of a triangle, but cannot accurately measure the angles. It is also real worldly in that it does not necessarily have to deal only with a right angles triangle. Joy!

So, I am a surveyor and I measure the sides of a piece of land. The dimensions are;

a = 75 m, b = 110 m, c = 60...


...but I don't know what any of the angles in the corners are. So, the first thing to remember about the Cosine Rule (and indeed, the Sine Rule) is that it always refers to the side opposite the angle. In other words, the angle that "makes" the other side. For example, angle (A), by virtue of the lengths of side (b) and (c), makes side (a).

So I want to find angle (A). The formula is this, and it is saying, if you read it, almost exactly what I wrote in words above;

( ( c² + b² ) - a² ) / (2 x c x b)

Let me do that;

( ( 60² + 110²) - 75² )  /  ( 2 x 60 x 110)

( ( 3600 + 12100 ) - 5625 )  /  13200

( 15700 - 5625 )  /  13200

10075  /  13200  =  0.76325

Okay, that answer is the Cosine of angle (A). Get the ArcCosine of that, and you have the angle.

Acos( 0.76325 ) = 40.25º

Ace, right? The other two angles are obtainable by the same method;

Cosine of Angle (C)  =  ( ( 110² + 75² ) - 60² )  /  ( 2 x 110 x 75 )

Cosine of Angle (B)  =  ( ( 60² + 75² ) - 110² )  /  ( 2 x 60 x 75 )

Sine Rule
Like the Cosine Rule, this handles non-right angles triangles, too. And it can be quite useful for some situations in games, especially for sorting out interception angles. But, before we get to that, what is Sine Rule, or at least, how does it work?

The Sine Rule states that (look at the figure above, in the Cosine Rule section again);

Side (a) / Sin(A) will give you the same answer as Side(b) / Sin(B), and Side (c) / Sin(C) is also the same thing.

a / Sin(A)  =  b / Sin(B)  =  c / Sin(C)

Well simple, then. Do notice, it all has to do with the sides opposite the angles, which stands to reason as we are dealing with Sine, after all. The Rule is used this way around to determine lengths of unknown sides. So, I know that;

Side (a) = 75 m, and I know Angle (A) = 40.25º and Angle (B) = 108.63º. This is enough information to give me the length of Side (b)...

75 / Sin(40.25º)  =  b / Sin(108.63)

116.077 = b / 0.9476

b = 116.077 x 0.9476

b = 109.99

With rounding errors, but yeah, 110 meters, which is right. You may call the result of 75 / Sin(40.25º) - that is, 116.077 - the "magic number" of this triangle, if you like. It is going to be the same result for any of the sides.

Want Side (c)? Simple. I know two angles and I know that the internal angles of ANY triangle add up to 180º. So straight off the bat, I can obtain Angle (C)...

C = 180º - (40.25º + 108.63º)

C = 31.12º

And with that, I am armed to keep using the Sine Rule to obtain the length of Side (c). I already know from the previous computation that the "magic number" is 116.077, so I may reuse it here...

c = 116.077 x Sin(31.12º)

c = 59.99 m

And that's that. Or is it? I can ALSO use the Sine Rule to determine ANGLES. It involves turning the Sine Rule on its head, like this...

Sin(A) / a  = Sin(B) / b  =  Sin(C) / c

So, I know two side and an angle. For example,

Side (a) = 75 m and Side(b) = 110 m, and Angle(B) = 108.63º

I want Angle (A)...

Sin(A) / 75  =  Sin(108.63º) / 110

Sin(A) / 75 = 0.00861456

Sin(A) = 75 x 0.00861456

Sin(A) = 0.6461

A = Asin(0.6461)

A = 40.25º

So yeah. That's about it, really. Here's another code snippet that uses Sine Rule to calculate an interception course. Have fun relearning in the future! I know you will...

Interception_Code

Bye for now!