Lesson 1: Angles and Trig Ratios

Builds on: arithmetic and square roots.

Leads to: Lesson 2, Vectors.


What we are trying to do

Many things move a known distance in a known direction: a robot driving across a floor, a ball leaving a launcher, a boat crossing a river. To say where any of them ends up, you have to split that move into parts along the directions you care about. This lesson is about that split.

The case we will carry through is a robot. A gyro reports which way it is facing, and encoders on the wheels report how far it has rolled. Neither reports where it is; turning a stream of headings and distances into a position is Lesson 6. The first step is a single move. The robot faces 30° to the left of straight down-field and drives 2.0 m forward. How far down-field has it gone, and how far sideways?

Getting there takes, in this order:


The picture

+x down-field +y left 30° 2.0 m down-field = ? sideways = ? The robot from above. Known: heading 30°, distance driven 2.0 m. Wanted: the two legs.

The amber arrow is the path the robot drove; the two dashed lines are the parts we want. Together they close into a triangle with a square corner in it, and everything that follows comes out of that triangle.


Intuition

Walk diagonally across a parking lot, heading somewhere between north and east. Part of every step goes north and part of it goes east. There is no such thing as a step that goes diagonally without going partly north.

Turn a little further toward east and you give up some north-progress and gain some east-progress. Turn all the way to east and the north part is gone. The trade between the two is set entirely by the angle you walk at, and not at all by how far you walk.

Ten steps or a hundred, at the same angle, the same fraction of the distance goes north. So what is worth knowing about an angle is a pair of fractions: how much of the distance goes north, and how much goes east. Find those two fractions for an angle, multiply each by the distance walked, and the split is done.


The theory, from first principles

Step 1. Where, and which way

“Where is the robot” needs a reference before it has an answer. Pick a point on the floor to call the origin, and two directions at right angles to each other through it, the axes. Any point on the floor is then named by two distances: how far along the first axis, and how far along the second. Those two numbers are its coordinates, written (x, y). Our robot starts at the origin, (0, 0), and the question is what its coordinates are after the move.

“Which way” needs a reference too. A heading of 30° on its own does not say 30° from what, or turned which way. An angle is an amount of turn. We measure it in degrees, where a full turn back to the starting direction is 360 degrees, written 360°. There is nothing special about 360: it is a convention inherited from Babylonian astronomy, kept because 360 divides evenly by so many numbers. Later in this lesson we meet a second unit that most software prefers.

Putting the two together is a frame: which direction is the x axis, which is the y axis, and which way of turning counts as positive. This is a choice, not a fact, and different fields and different software make different choices. The one used here is the common one in robotics software, including WPILib, and it is what the team’s gyro reports:

So a robot facing straight down-field has heading 0°, and our robot has heading +30°. From here on we call that number θ, the Greek letter theta, which is the traditional name for an angle.

Step 2. The triangle already in the picture

In the picture, the arrow of the drive, the dashed blue line that is its shadow on the down-field axis, and the dashed green line dropping from its tip form a closed three-sided shape.

A right triangle is a triangle with one angle of exactly 90°, called a right angle and marked in figures by a small square. The other two angles are each less than 90°. The side opposite the right angle, always the longest, is the hypotenuse; the other two are the legs.

The legs get names, but the names depend on which of the two non-right angles you mean. Pick one and call it θ. The opposite leg is the one that does not touch θ. The adjacent leg is the one that touches θ and is not the hypotenuse. So “hypotenuse” is fixed by the triangle, while “opposite” and “adjacent” are fixed by your choice of angle, and choosing the other non-right angle swaps them.

θ adjacent opposite hypotenuse angle chosen at the left vertex θ opposite adjacent hypotenuse angle chosen at the top vertex

In our picture, θ sits at the start, the corner where the arrow and the down-field axis meet. So the down-field part is the adjacent leg, the sideways part is the opposite leg, and the distance driven is the hypotenuse. The problem now has a clean statement: given the hypotenuse, 2.0 m, and the angle, 30°, find the two legs.

Step 3. Why the fraction depends on the angle alone

This is the step everything else rests on: the claim from the parking lot that the trade between the two parts is set by the angle and not by the distance. Here is why it is true.

First, scaling. Take any right triangle and multiply every side by the same number k, keeping the angles the same. You get a bigger or smaller copy of the same shape. Now look at a ratio of two of its sides, say the opposite leg divided by the hypotenuse:

\[\frac{k \times \text{opposite}}{k \times \text{hypotenuse}} = \frac{\text{opposite}}{\text{hypotenuse}}\]

The k cancels top and bottom, so the ratio survives scaling untouched.

Second, angles. The three angles of any triangle add up to 180°. To convince yourself rather than take it on faith, cut a triangle out of paper, tear off its three corners, and lay them side by side with their points touching: they fill a straight line, and a straight line is a half turn, which is 180°.

Now take two right triangles that share one of their non-right angles, say both have a 30° in them. Each has a 90°, so each has a third angle of 180° − 90° − 30° = 60°. All three angles match. Two triangles with all three angles equal are called similar, and similar triangles are scaled copies of one another: one is the other blown up or shrunk by some factor k.

Put the two facts together. Any two right triangles with the same acute angle are scaled copies, and scaling does not change a ratio of sides. Therefore the ratio of any two sides of a right triangle is fixed by the angle and by nothing else. Not by size, and not by how far you went.

A numeric check. A right triangle with legs 90 and 120 and hypotenuse 150 is the familiar 3-4-5 shape scaled up by 30. Taking the angle at the corner touching the side of length 120:

\[\begin{aligned} \frac{\text{opposite}}{\text{hypotenuse}} &= \frac{90}{150} = 0.600 \cr \frac{\text{adjacent}}{\text{hypotenuse}} &= \frac{120}{150} = 0.800 \cr \frac{\text{opposite}}{\text{adjacent}} &= \frac{90}{120} = 0.750 \end{aligned}\]

Halve every side to 45, 60 and 75 and the ratios come out 45/75 = 0.600, 60/75 = 0.800 and 45/60 = 0.750. Unchanged, as promised.

Step 4. Naming the ratios

Because each ratio depends only on the angle, each ratio is a function of the angle: feed in an angle, get back one fixed number. All three of them are used often enough to have names.

\[\begin{aligned} \sin \theta &= \frac{\text{opposite}}{\text{hypotenuse}} \cr \cos \theta &= \frac{\text{adjacent}}{\text{hypotenuse}} \cr \tan \theta &= \frac{\text{opposite}}{\text{adjacent}} \end{aligned}\]

The standard mnemonic is SOH-CAH-TOA: Sine is Opposite over Hypotenuse, Cosine is Adjacent over Hypotenuse, Tangent is Opposite over Adjacent.

Math!

sin θ is read “sine theta”, cos θ is “cosine theta”, tan θ is “tangent theta”. The name and the angle are written next to each other with no symbol between them, which looks like multiplication but is not. sin θ does not mean “sin times θ”; it means “the sine function applied to the angle θ”, and on its own sin is not a number at all. Some writers add brackets, sin(θ), and that means the same thing. One more piece of shorthand you will meet in Step 8: sin²θ means (sin θ)², that is, take the sine first and then square the result. It does not mean the sine of θ².

For a heading, tangent has a plain reading: sideways meters per down-field meter. At 30° it is 0.577, so the robot slides 0.577 m sideways for every 1 m it makes down-field. Tangent has no upper limit. As the heading approaches 90°, straight sideways, down-field progress approaches zero and the ratio runs away: tan 89° = 57.29, tan 89.9° = 572.96, tan 89.99° = 5729.6. At exactly 90° it is undefined, which is correct rather than broken, since a robot going straight sideways makes no down-field progress to divide by.

Tangent also runs backwards: given the two components, its inverse recovers the angle. That is how Lesson 2 turns a pair of numbers back into a heading.

Step 5. Solving the opening problem

We know the hypotenuse and want the legs, so multiply both sides of each definition by the hypotenuse:

\[\text{adjacent} = \text{hypotenuse} \times \cos \theta \qquad \text{opposite} = \text{hypotenuse} \times \sin \theta\]

Those are the parking lot’s two fractions, now with names. Put in our numbers, with the hypotenuse being the distance driven, 2.0 m, and θ being 30°:

\[\text{down-field} = 2.0 \times \cos 30° = 2.0 \times 0.866 = 1.732 \text{ m}\] \[\text{sideways} = 2.0 \times \sin 30° = 2.0 \times 0.500 = 1.000 \text{ m}\]

So the robot is 1.732 m down-field and 1.000 m to the left of where it started: its coordinates are (1.732, 1.000) m. That is the opening problem, solved, except for one thing. A calculator or a software library supplied the two numbers 0.866 and 0.500. Where they come from, how to get them for a heading that will not fit inside a right triangle at all, and how to check the answer, is the rest of the theory.

Step 6. Every heading, not just the ones in a triangle

The non-right angles of a right triangle are each under 90°, so everything from Step 2 onward covers only headings between 0° and 90°. A robot can face anywhere, including backwards.

Fix it by shrinking the triangle to a standard size. Divide every side by the hypotenuse. By Step 3 the ratios do not change, but the hypotenuse becomes hypotenuse/hypotenuse = 1, and the two legs become

\[\frac{\text{adjacent}}{\text{hypotenuse}} = \cos \theta \qquad \frac{\text{opposite}}{\text{hypotenuse}} = \sin \theta\]

In a triangle whose hypotenuse is 1, the legs are no longer proportional to the ratios. They are the ratios.

Now place that triangle on a grid, with θ at the origin and the adjacent leg along the positive x axis. The tip of the hypotenuse sits at the point (cos θ, sin θ), always exactly 1 unit from the origin. Sweep θ around and that point traces a circle of radius 1 centered on the origin: the unit circle.

This gives us the definition we were missing. For any angle at all, cos θ and sin θ are the x and y coordinates of the point you reach by starting at (1, 0) and turning counter-clockwise by θ around the unit circle. Between 0° and 90° this agrees with the triangle ratios. Beyond 90° there is no triangle to appeal to, and this is simply the definition.

1 −1 1 −1 I: cos + sin + II: cos − sin + III: cos − sin − IV: cos + sin − 220° 40° cos = −0.766 sin = −0.643 θ = 220°: the point is (−0.766, −0.643)

Take θ = 220°, drawn above. Turning counter-clockwise by 220° from the positive x axis puts the point down and to the left of the origin, in the third of the four quarters of the plane. Those quarters are called quadrants, numbered I to IV counter-clockwise starting from the upper right.

To read off the numbers, drop a perpendicular from the point to the x axis. That makes a right triangle again, and the angle it makes at the origin, between the radius and the nearest part of the x axis, is 220° − 180° = 40°. This is called the reference angle. The triangle’s legs have lengths cos 40° = 0.766 and sin 40° = 0.643. The signs come from which side of each axis the point sits on: left of the vertical axis, so its x coordinate is negative, and below the horizontal axis, so its y coordinate is negative. Hence cos 220° = −0.766 and sin 220° = −0.643.

The same reading works in every quadrant, and it always comes down to this table:

Quadrant Angles cos θ sin θ
I 0° to 90° + +
II 90° to 180° − +
III 180° to 270° − −
IV 270° to 360° + −

Now a worked example. The robot drives 1.5 m at heading 220°. Step 5’s formulas do not change at all:

\[\text{down-field} = 1.5 \times \cos 220° = 1.5 \times (-0.766) = -1.149 \text{ m}\] \[\text{sideways} = 1.5 \times \sin 220° = 1.5 \times (-0.643) = -0.964 \text{ m}\]

Both are negative. Under our sign convention, negative down-field means up-field, back toward our own end, and negative sideways means to the right, which is exactly what a heading of 220° should mean. The formulas needed no special case; the unit circle handled the signs.

One more property falls straight out of the picture. A full 360° brings you back to the same point on the circle, so 220°, 220° − 360° = −140°, and 220° + 360° = 580° all name the same direction and all give the same cosine and sine. The functions repeat every 360°. That is convenient here and a nuisance later, because one heading then has infinitely many numerical names. Lesson 3 deals with what that costs you when comparing two headings.

Step 7. Radians

We are about to hand these angles to software, and the library functions that compute sines and cosines do not take degrees. They take radians.

Go back to the unit circle. Instead of measuring an angle by what fraction of an arbitrary 360 it is, measure it by the distance you walk along the rim while turning. That distance is the angle in radians.

1 rad radius = 1 arc length = 1 Walk 1 unit along the rim of a unit circle: that turn is 1 radian, about 57.3°.

Walking all the way around a circle of radius 1 covers its circumference, 2π, roughly 6.283. So a full turn is 2π radians, a half turn is π radians, and a quarter turn is π/2 radians. Setting the two units for a full turn equal, 360° = 2π rad, gives the conversions:

\[\text{radians} = \text{degrees} \times \frac{\pi}{180} \qquad \text{degrees} = \text{radians} \times \frac{180}{\pi}\]

So our heading 30° is 30 × π/180 = 0.5236 rad, the heading 220° from the last example is 220 × π/180 = 3.840 rad, and 1 rad is 1 × 180/π = 57.2958°.

Math!

On a circle of radius r rather than 1, an angle θ cuts off an arc whose length s is given by s = r θ, read “s equals r theta”, and the θ must be in radians. This is the reason radians are worth the trouble: with them, arc length is just radius times angle, with no conversion factor stuck in front. With degrees you would have to write s = r θ π / 180 every time. That single clean formula is how a wheel encoder’s count of turns becomes meters rolled across the floor, which appears in the team’s code at the end of this lesson.

Step 8. Did the split lose any distance?

We claimed the two parts account for all of the robot’s 2.0 m. That deserves a check, because if the split leaked distance then every position built on it would drift.

The check needs one classical result, and since we are assuming nothing, here is where it comes from. Draw a square of side a + b, where a and b are the two legs of our right triangle, and fit four copies of that triangle inside it in two different arrangements.

c² a b c a² b² Same square, same four triangles: c² = a² + b².

On the left, the four triangles sit in the corners and the space they leave in the middle is a tilted square whose side is the hypotenuse c, so the leftover area is c². On the right, the same four triangles are regrouped into two rectangles, and the space they leave is two squares, of sides a and b, so the leftover area is a² + b². The outer square is the same in both pictures and the four triangles have the same total area in both, so the leftovers are equal:

\[c^2 = a^2 + b^2\]

That is the Pythagorean theorem, and it is now ours rather than borrowed.

Apply it to the unit circle. The point at angle θ is cos θ across and sin θ up from the origin, with the radius as hypotenuse, and the radius is 1. So:

\[\cos^2\theta + \sin^2\theta = 1\]

This holds for every angle in every quadrant, because squaring wipes out the signs. Now scale it back up to a real distance d by multiplying both sides by d²:

\[(d \cos \theta)^2 + (d \sin \theta)^2 = d^2\]

The left side is exactly our two parts, squared and added. Check it with our numbers:

\[1.732^2 + 1.000^2 = 3.000 + 1.000 = 4.000 \qquad \sqrt{4.000} = 2.000 \text{ m}\]

The two parts recombine to the full 2.0 m: the robot ends up exactly as far from the start as it drove. The split lost nothing.

Step 9. When the hypotenuse is a speed

Nothing in Steps 2 to 8 used the fact that the hypotenuse was a distance. Any quantity with a size and a direction splits the same way, and one that comes up constantly is a speed. A robot doing 2.0 m/s at heading 30° is making 2.0 × 0.866 = 1.732 m/s down-field and 2.0 × 0.500 = 1.000 m/s sideways: Step 5’s arithmetic with different units on the end.

The direction does not even have to lie on the floor. A ball leaves a shooter at 12 m/s, aimed 55° above the horizontal. Here cos 55° = 0.5736 and sin 55° = 0.8192, so the ball moves forward at 12 × 0.5736 = 6.883 m/s and upward at 12 × 0.8192 = 9.830 m/s, and the check holds: √(6.883² + 9.830²) = 12.000. Lesson 5, on how a ball flies, starts from exactly those two numbers, and Lesson 6 defines speed properly, as a rate of change of position.


Try it

Drag the heading around, change the distance, and watch the two dashed legs and the robot’s end position move with it.


In code

Tier 1, plain Java with WPILib Units

import static edu.wpi.first.units.Units.*;
import edu.wpi.first.units.measure.Angle;
import edu.wpi.first.units.measure.Distance;

Angle heading = Degrees.of(30.0);
Distance driven = Meters.of(2.0);

// Math.cos and Math.sin take radians. The Units library does the conversion.
double cosHeading = Math.cos(heading.in(Radians));   // 0.866
double sinHeading = Math.sin(heading.in(Radians));   // 0.500

Distance downField = driven.times(cosHeading);       // 1.732 m
Distance sideways  = driven.times(sinHeading);       // 1.000 m

System.out.printf("down-field %.3f m, sideways %.3f m%n", downField.in(Meters), sideways.in(Meters));

This is Step 5 typed out line for line. The point of the Angle and Distance types is that each quantity carries its unit with it, so a heading measured in degrees cannot be handed to Math.cos by accident: the compiler will not let an Angle stand where a double is wanted. The one place the conversion happens is heading.in(Radians), which is visible, deliberate, and easy to find later. All three tiers produce the same 1.732 and 1.000.

Tier 2, WPILib geometry

import edu.wpi.first.math.geometry.Rotation2d;
import edu.wpi.first.math.geometry.Translation2d;

Rotation2d heading = new Rotation2d(Degrees.of(30.0));   // or Rotation2d.fromDegrees(30.0)
double drivenMeters = 2.0;

double downFieldMeters = drivenMeters * heading.getCos();   // 1.732
double sidewaysMeters  = drivenMeters * heading.getSin();   // 1.000

// The same split in one call: a distance pointed along a direction.
Translation2d position = new Translation2d(drivenMeters, heading);   // (1.732, 1.000) m

Rotation2d is WPILib’s type for an angle. It stores the angle and caches its cosine and sine at construction, so getCos() and getSin() are reads of a stored number. That matters in a loop that runs fifty times a second: nothing recomputes a trig function it already has. The constructor new Translation2d(distance, angle) is Step 5 packaged as one call, and WPILib even names its first parameter distance. It takes a length and a direction and hands back the pair of coordinates. Lesson 2 gives that pair its proper name, a vector.

Tier 3, the team’s code

These two lines are from Odometry.java in the team’s robots repository.

currentWheelRotation[i] =
        Rotation2d.fromDegrees(gyro.getAngle()).plus(moduleList[i].getSteerAngle());
Translation2d wheelMotion =
        new Translation2d(deltaX, deltaY).rotateBy(prevWheelRotation[i]);

Each wheel on this robot can be steered independently, so a wheel does not necessarily point where the robot points. Its direction on the field is the robot’s heading, read from the gyro, plus how far that wheel is turned relative to the robot, read from its steering sensor. That is the first line.

In the second line, deltaX is how far that wheel rolled since the last loop. It comes from the wheel’s number of turns times its circumference, 2πr: each full turn is 2π radians, so this is Step 7’s arc length s = rθ with the angle in radians. For a wheel that rolled straight ahead, deltaY is zero, so the point being rotated is (deltaX, 0), and turning it to the wheel’s field direction gives exactly (deltaX · cos θ, deltaX · sin θ). That is the same split, once per wheel, fifty times a second. Lesson 3 covers what rotateBy does when deltaY is not zero, and Lesson 6 covers adding these little steps up into a position on the field.


Recap

You can now turn a heading and a distance into a down-field part and a sideways part, using cosine for the leg along the heading’s axis and sine for the leg across it. You know why those two fractions depend on the angle and not on the distance: right triangles sharing an acute angle are scaled copies, and scaling cancels out of a ratio. You can do it for any heading, not just the ones under 90°, by reading cos θ and sin θ off the unit circle and taking the signs from the quadrant. You can convert between degrees and radians in either direction, and you know why the library wants radians. And you can check your own work, because cos²θ + sin²θ = 1 guarantees the two parts recombine to the whole distance. And the same split works unchanged when the hypotenuse is a speed instead.

Re-derive it from a blank page

Come back to this in about a week, take a blank sheet, and do these three without looking:

  1. Show why the ratio of two sides of a right triangle depends on the angle alone.
  2. Starting from the unit circle and Pythagoras, get cos²θ + sin²θ = 1.
  3. A robot drives 1.5 m at heading 220°. Find both parts of the move, and check them.

Lessons
Lesson 2: Vectors →