Monocurl Essays
Series Download

Angle addition formulas: sin(a + b) from one picture

The sine of a sum is not the sum of the sines. Two stacked right triangles show what it is, and a rotated chord proves the formulas for every angle.

20 min·High school·

AssumesThe unit circle: sine and cosine as coordinatesPythagorean identity: why sin² + cos² = 1

Two right triangles are stacked, the lower with angle $\alpha$ and the upper with angle $\beta = 30°$ and hypotenuse 1, and a rectangle is drawn around them. As $\alpha$ changes, the green left side has length $\sin(\alpha + \beta)$ and always equals the blue and orange pieces of the right side together.
Monocurl source
let O = [-2.7, -1.75, 0]
let U = 3.6
let BETA = rad(30)
mesh names = [
    Name("\sin(\alpha+\beta)", [1.9, 1.6, 0], 0.85, LEAF),
    Name("\sin\alpha\,\cos\beta", [1.9, 0.4, 0], 0.85, AZURE),
    Name("\cos\alpha\,\sin\beta", [1.9, -0.8, 0], 0.85, EMBER)
]
let Rig = |deg| {
    let a = rad(deg)
    let s = Stack(O, U, a, BETA)
    return [
        StackFills(s),
        Wedge(O, 0, a, SLATE, 0.85),
        Wedge(O, a, BETA, SLATE, 0.62),
        StackLines(s),
        Name("\alpha", O + 1.3 * dir(a / 2), 0.95, SLATE),
        Name("\beta", O + 1.05 * dir(a + BETA / 2), 0.95, SLATE),
        center{[1.9, 1.05, 0]} scale{1.5} color{LEAF} Number(sin(a + BETA), 3),
        center{[1.9, -0.15, 0]} scale{1.5} color{AZURE} Number(sin(a) * cos(BETA), 3),
        center{[1.9, -1.35, 0]} scale{1.5} color{EMBER} Number(cos(a) * sin(BETA), 3)
    ]
}
mesh rig = Rig(deg: 18)

slide "sweep"
    play Wait(0.6)
    rig.deg = 45
    play Lerp(3, [&rig])
    rig.deg = 25
    play Lerp(2.5, [&rig])
    play Wait(1.6)

Uses the shared definitions for this lesson's figures.

You know that $\sin 30° = 0.5$ and $\sin 60° \approx 0.866$. What is $\sin(30° + 60°)$? If sine treated a sum the way multiplication by a number does, the answer would be $0.5 + 0.866 = 1.366$. But $30° + 60° = 90°$, and $\sin 90° = 1$. The guess is wrong, and it could not have been right, because a sine is the height of a point on a circle of radius 1 and no such point is 1.366 above the center.

The graph of $\sin\theta$ from 0° to 96°. The heights at 30° and 60° are carried over to 90° and stacked. Together they reach 1.366, well above the true value $\sin 90° = 1$.
Monocurl source
let fr = Frame(0, 96, 0, 1.5, [-0.75, 0.05, 0], 5.0, 3.3)
let S30 = sin(rad(30))
let S60 = sin(rad(60))
mesh grid = GridLines(fr, [30, 60, 90], [0.5, 1, 1.5])
mesh axes = [
    Axes(fr),
    DegTicks(fr, [30, 60, 90]),
    YTicks(fr, [0.5, 1, 1.5], SLATE, 1),
    Name("\theta", [fx(fr, 96) + 0.5, fy(fr, 0) - 0.02, 0], 0.85, SLATE),
    Name("\sin\theta", [fx(fr, 0) + 0.65, fy(fr, 1.5) + 0.3, 0], 0.85, SLATE)
]
mesh curve = Graph(fr, |d| sin(rad(d)), 0, 96, SLATE, 3.5)
mesh ghosts = [
    Seg(at(fr, 30, 0), at(fr, 30, S30), tint(AZURE, 0.4), 6),
    Seg(at(fr, 60, 0), at(fr, 60, S60), tint(EMBER, 0.4), 6),
    Name(to_string(S30, 1), at(fr, 30, 0.2) + [-0.4, 0, 0], 0.76, AZURE),
    Name(to_string(S60, 3), at(fr, 60, 0.3) + [-0.55, 0, 0], 0.76, EMBER)
]
let Carry = |u| [
    Seg(at(fr, 30 + 60 * u, 0), at(fr, 30 + 60 * u, S30), AZURE, 6),
    Seg(at(fr, 60 + 30 * u, S30 * u), at(fr, 60 + 30 * u, S30 * u + S60), EMBER, 6)
]
mesh carry = Carry(u: 0)
mesh truth = [
    Pt(at(fr, 90, 1), LEAF, 0.11),
    Name("\sin 90^\circ = 1", [2.85, fy(fr, 1), 0], 0.8, LEAF)
]
mesh verdict = []

slide "stack"
    play Wait(0.8)
    carry.u = 1
    play Lerp(2.5, [&carry])
    verdict = [
        Pt(at(fr, 90, S30 + S60), ROSE, 0.11),
        Name(str_join([to_string(S30, 1), " + ", to_string(S60, 3)]), [2.85, fy(fr, S30 + S60) + 0.25, 0], 0.8, ROSE),
        Name(str_join(["= ", to_string(S30 + S60, 3)]), [2.85, fy(fr, S30 + S60) - 0.2, 0], 0.8, ROSE)
    ]
    play Fade(0.6, [&verdict])
    play Wait(2.2)

Uses the shared definitions for this lesson's figures.

Still, the point at 90° is reached by turning 30° and then 60° more, so its position has to be determined somehow by what we know about 30° and 60°. The question of this lesson is how. Given the sines and cosines of two angles $\alpha$ and $\beta$, what are $\sin(\alpha + \beta)$ and $\cos(\alpha + \beta)$? The answer gives exact values for new angles such as 75° and 15°, it contains the double angle formulas, and it is the step on which the derivative of sine and the addition of waves both rest.

What we are allowed to use

Two facts from the unit circle lesson carry the whole argument. The first is the definition: for any angle $\theta$, turning the point $(1, 0)$ about the origin through $\theta$ brings it to the point $(\cos\theta, \sin\theta)$. The second is what this says about a right triangle. If a right triangle has hypotenuse 1 and an acute angle $\theta$, the leg next to the angle has length $\cos\theta$ and the leg across from it has length $\sin\theta$. Scale the triangle so that its hypotenuse is $c$ and both legs scale with it, to $c\cos\theta$ and $c\sin\theta$.

That second fact converts a length along a slanted line into a horizontal part and a vertical part. We will need it three times.

A first case: 30° plus 45°

Before any letters, take two angles whose sines and cosines we know exactly, 30° and 45°, and look for $\sin 75°$. By definition it is the height of the point $P$ that lies 1 unit from the origin $O$ in the direction 75°. Nothing we know speaks about 75° directly. What we do know about are right triangles with a 30° angle or a 45° angle, so the plan is to reach $P$ using only those.

  1. The point P is 1 unit from O in the direction 75°, so its height above the base line is sin 75°. The 75° is built as 30° and then 45° more.
  2. Drop a perpendicular from P to the 30° ray, meeting it at B. Triangle OBP has hypotenuse 1 and a 45° angle at O, so OB = cos 45° ≈ 0.707 and BP = sin 45° ≈ 0.707.
  3. Drop a perpendicular from B to the base line at A. Triangle OAB has hypotenuse 0.707 and a 30° angle at O, so B is 0.707 × sin 30° ≈ 0.354 above the base.
  4. Close the rectangle through O, A and P. In the corner triangle BCP the angle at B is 30° again and the hypotenuse is BP ≈ 0.707, so P is 0.707 × cos 30° ≈ 0.612 above B.
  5. The left side OD is the height of P, and it is as long as the right side AC. So sin 75° ≈ 0.354 + 0.612 = 0.966.
Finding $\sin 75°$ by stacking a 45° right triangle with hypotenuse 1 on top of a 30° right triangle. The height of the top corner $P$ is the sum of two vertical pieces, 0.354 and 0.612.
Monocurl source
let O = [-2.3, -1.6, 0]
let U = 3.4
let AL = rad(30)
let BE = rad(45)
let s = Stack(O, U, AL, BE)
let cA = s[1]
let cB = s[2]
let cP = s[3]
let cC = s[4]
let cD = s[5]
let foot = [cP[0], O[1], 0]
let num = |v| to_string(v, 3)
let along = dir(AL)
let across = dir(AL + PI / 2)

mesh fill_low = []
mesh fill_mid = []
mesh fill_top = []
mesh rect = []
mesh rig = [
    Seg(O + [-0.3, 0, 0], O + [2.7, 0, 0], SLATE, 2),
    Seg(O, O + 3.0 * along, MIST, 2.5),
    Wedge(O, 0, AL, SLATE, 0.85),
    Wedge(O, AL, BE, SLATE, 0.62),
    Seg(O, cP, INK, 3),
    Pt(cP, INK, 0.09),
    Name("30^\circ", O + 1.3 * dir(AL / 2), 0.72, SLATE),
    Name("45^\circ", O + 1.05 * dir(AL + BE / 2), 0.72, SLATE),
    Name("1", 0.5 * (O + cP) + [-0.25, 0.05, 0], 0.8),
    Name("O", O + [-0.27, -0.2, 0], 0.76),
    Name("P", cP + [-0.05, 0.3, 0], 0.76)
]
mesh height = []
mesh stage2 = []
mesh stage3 = []
mesh stage4 = []
mesh stage5 = []
mesh ledger = [
    center{[2.25, 1.6, 0]} Note("height of P above O", 0.74),
    Name("\sin 75^\circ", [2.25, 1.05, 0], 0.95, LEAF)
]
mesh ask = []
mesh sum1 = []
mesh sum2 = []
mesh sum3 = []

slide "The point P is 1 unit from O in the direction 75°, so its height above the base line is sin 75°. The 75° is built as 30° and then 45° more."
    height = Seg(foot, cP, LEAF, 4)
    ask = Name("?", [2.25, 0.2, 0], 1.2, LEAF)
    play Fade(0.6)

slide "Drop a perpendicular from P to the 30° ray, meeting it at B. Triangle OBP has hypotenuse 1 and a 45° angle at O, so OB = cos 45° ≈ 0.707 and BP = sin 45° ≈ 0.707."
    height = []
    ask = []
    play Fade(0.4)
    fill_mid = Region([O, cB, cP], tint(SLATE, 0.1))
    stage2 = [
        Seg(O, cB, INK, 3),
        Seg(cB, cP, INK, 3),
        Square90(cB, -1 * along, across, INK),
        Pt(cB, INK, 0.08),
        Name("B", cB + [0.28, -0.05, 0], 0.76),
        Name(num(cos(BE)), O + 0.68 * (cB - O) + 0.36 * across, 0.76),
        Name(num(sin(BE)), 0.5 * (cB + cP) - 0.5 * along, 0.76)
    ]
    play Fade(0.7)

slide "Drop a perpendicular from B to the base line at A. Triangle OAB has hypotenuse 0.707 and a 30° angle at O, so B is 0.707 × sin 30° ≈ 0.354 above the base."
    fill_low = Region([O, cA, cB], tint(SLATE, 0.28))
    stage3 = [
        Seg(cA, cB, AZURE, 6),
        Square90(cA, [-1, 0, 0], [0, 1, 0], SLATE),
        Name("A", cA + [0.25, -0.2, 0], 0.76),
        Name(num(cos(BE) * sin(AL)), 0.5 * (cA + cB) + [0.6, 0, 0], 0.8, AZURE),
        Name(num(cos(BE) * cos(AL)), 0.5 * (O + cA) + [0.2, -0.3, 0], 0.76, SLATE)
    ]
    sum1 = Name(num(cos(BE) * sin(AL)), [2.3, 0.35, 0], 1.0, AZURE)
    play Fade(0.7)

slide "Close the rectangle through O, A and P. In the corner triangle BCP the angle at B is 30° again and the hypotenuse is BP ≈ 0.707, so P is 0.707 × cos 30° ≈ 0.612 above B."
    rect = Outline([O, cA, cC, cD], SLATE, 2)
    fill_top = Region([cB, cC, cP], tint(SLATE, 0.28))
    stage4 = [
        Wedge(cB, PI / 2, AL, SLATE, 0.7),
        Seg(cB, cP, INK, 3),
        Seg(cB, cC, EMBER, 6),
        Square90(cC, [-1, 0, 0], [0, -1, 0], SLATE),
        Name("30^\circ", cB + 1.3 * dir(PI / 2 + AL / 2), 0.72, SLATE),
        Name("C", cC + [0.25, 0.15, 0], 0.76),
        Name("D", cD + [-0.25, 0.15, 0], 0.76),
        Name(num(sin(BE) * cos(AL)), 0.5 * (cB + cC) + [0.6, 0, 0], 0.8, EMBER)
    ]
    sum2 = [
        Name("+", [1.55, -0.2, 0], 1.0),
        Name(num(sin(BE) * cos(AL)), [2.3, -0.2, 0], 1.0, EMBER)
    ]
    play Fade(0.7)

slide "The left side OD is the height of P, and it is as long as the right side AC. So sin 75° ≈ 0.354 + 0.612 = 0.966."
    stage5 = [
        Seg(O, cD, LEAF, 6),
        Name(num(sin(AL + BE)), 0.5 * (O + cD) + [-0.55, 0, 0], 0.8, LEAF)
    ]
    sum3 = [
        Seg([1.35, -0.6, 0], [2.95, -0.6, 0], INK, 2.5),
        Name(num(sin(AL + BE)), [2.3, -1.05, 0], 1.1, LEAF)
    ]
    play Fade(0.7)

Uses the shared definitions for this lesson's figures.

Each step used the triangle fact once. From $P$ we dropped a perpendicular to the 30° ray, because that makes a right triangle $OBP$ with the 45° angle in it and hypotenuse 1: its legs are $OB = \cos 45°$ and $BP = \sin 45°$, both $\frac{\sqrt 2}{2} \approx 0.707$. That locates $P$ by going 0.707 along the slanted ray and then 0.707 at right angles to it. Height is measured vertically, so each of those two slanted segments has to be converted into its vertical part.

The segment $OB$ is the hypotenuse of triangle $OAB$, which has the 30° angle at $O$. Its vertical leg is $AB = 0.707 \times \sin 30° \approx 0.354$. The segment $BP$ is the hypotenuse of the corner triangle $BCP$. The angle of that triangle at $B$ is 30° too, for this reason: along the vertical line through $A$, $B$ and $C$, the three angles at $B$ add to 180°. One is the angle of triangle $OAB$ at $B$, which is $90° - 30° = 60°$. The next is the right angle $OBP$. That leaves $180° - 60° - 90° = 30°$. So the vertical leg is $BC = 0.707 \times \cos 30° \approx 0.612$.

The exact values are $\frac{\sqrt 2}{2} \cdot \frac{1}{2}$ and $\frac{\sqrt 2}{2} \cdot \frac{\sqrt 3}{2}$, so

$$ \sin 75° = \frac{\sqrt 2}{4} + \frac{\sqrt 6}{4} = \frac{\sqrt 2 + \sqrt 6}{4} \approx 0.9659, $$

which agrees with a calculator.

Pause and try

The same rectangle contains $\cos 75°$. It is the length of the top side from $D$ to $P$. Find it, using the bottom side $OA$ and the short piece $PC$ of the top side.

Show answerHide answer

The bottom side is the horizontal leg of triangle $OAB$: $OA = \frac{\sqrt 2}{2}\cos 30° \approx 0.6124$. The piece $PC$ is the horizontal leg of triangle $BCP$: $PC = \frac{\sqrt 2}{2}\sin 30° \approx 0.3536$. The top side $DC$ is as long as the bottom side, so $DP = OA - PC \approx 0.6124 - 0.3536 = 0.2588$. Exactly, $\cos 75° = \frac{\sqrt 6 - \sqrt 2}{4} \approx 0.2588$. That $DP$ is the cosine of 75° follows from triangle $ODP$, which has hypotenuse $OP = 1$ and, at $P$, the angle between $OP$ and the horizontal, which is 75°.

The same picture with letters

Nothing in that computation depended on the angles being 30° and 45°, only on their sum being less than 90°, so that the stacked triangles stay inside one quarter of the plane.

Pause and try

Redraw the rectangle with the lower angle called $\alpha$ and the upper angle called $\beta$, keeping $OP = 1$. Label $OB$, $BP$, and then all six segments on the boundary of the rectangle: $AB$, $BC$, $OA$, $PC$, $OD$ and $DP$.

Show answerHide answer

$OB = \cos\beta$ and $BP = \sin\beta$. Triangle $OAB$ has hypotenuse $\cos\beta$ and angle $\alpha$, so $OA = \cos\alpha\cos\beta$ and $AB = \sin\alpha\cos\beta$. Triangle $BCP$ has hypotenuse $\sin\beta$ and angle $\alpha$ at $B$, so $BC = \cos\alpha\sin\beta$ and $PC = \sin\alpha\sin\beta$. Triangle $ODP$ has hypotenuse 1 and angle $\alpha + \beta$ at $P$, so $OD = \sin(\alpha+\beta)$ and $DP = \cos(\alpha+\beta)$. The figure below shows all of them.

The stacked triangles for general acute angles \alpha and \beta (drawn with 25° and 30°). The left side \sin(\alpha+\beta) equals the two pieces of the right side, and the bottom side \cos\alpha\cos\beta equals the two pieces of the top side.
The stacked triangles for general acute angles $\alpha$ and $\beta$ (drawn with 25° and 30°). The left side $\sin(\alpha+\beta)$ equals the two pieces of the right side, and the bottom side $\cos\alpha\cos\beta$ equals the two pieces of the top side.
Monocurl source
let O = [-1.6, -1.5, 0]
let U = 3.9
let AL = rad(25)
let BE = rad(30)
let s = Stack(O, U, AL, BE)
let cA = s[1]
let cB = s[2]
let cP = s[3]
let cC = s[4]
let cD = s[5]
let along = dir(AL)
let across = dir(AL + PI / 2)
mesh fills = StackFills(s)
mesh wedges = [
    Wedge(O, 0, AL, SLATE, 0.95),
    Wedge(O, AL, BE, SLATE, 0.7),
    Wedge(cB, PI / 2, AL, SLATE, 0.7),
    Wedge(cP, PI, AL + BE, SLATE, 0.5)
]
mesh lines = [
    StackLines(s),
    Seg(O, cA, PLUM, 6),
    Seg(cD, cP, LEAF, 6),
    Seg(cP, cC, GOLD, 6),
    Square90(cB, -1 * along, across, INK),
    Square90(cA, [-1, 0, 0], [0, 1, 0], SLATE),
    Square90(cC, [-1, 0, 0], [0, -1, 0], SLATE),
    Square90(cD, [1, 0, 0], [0, -1, 0], SLATE),
    Pt(cP, INK, 0.09)
]
mesh names = [
    Name("\alpha", O + 1.35 * dir(AL / 2), 0.8, SLATE),
    Name("\beta", O + 1.1 * dir(AL + BE / 2), 0.8, SLATE),
    Name("\alpha", cB + 1.05 * dir(PI / 2 + AL / 2), 0.8, SLATE),
    Name("\alpha+\beta", cP + 1.15 * dir(PI + (AL + BE) / 2), 0.76, SLATE),
    Name("1", 0.55 * (O + cP) + [-0.1, 0.25, 0], 0.8),
    Name("\cos\beta", O + 0.66 * (cB - O) + 0.36 * across, 0.76),
    Name("\sin\beta", 0.5 * (cB + cP) - 0.45 * along, 0.76),
    Name("\sin(\alpha+\beta)", 0.5 * (O + cD) + [-1.0, 0, 0], 0.8, LEAF),
    Name("\sin\alpha\cos\beta", 0.5 * (cA + cB) + [1.05, 0, 0], 0.8, AZURE),
    Name("\cos\alpha\sin\beta", 0.5 * (cB + cC) + [1.05, 0, 0], 0.8, EMBER),
    Name("\cos\alpha\cos\beta", 0.5 * (O + cA) + [0, -0.33, 0], 0.8, PLUM),
    Name("\cos(\alpha+\beta)", 0.5 * (cD + cP) + [-0.2, 0.32, 0], 0.8, LEAF),
    Name("\sin\alpha\sin\beta", 0.5 * (cP + cC) + [0.75, 0.32, 0], 0.8, GOLD),
    Name("O", O + [-0.25, -0.2, 0], 0.72),
    Name("A", cA + [0.25, -0.2, 0], 0.72),
    Name("B", cB + [0.25, 0, 0], 0.72),
    Name("C", cC + [0.25, -0.2, 0], 0.72),
    Name("D", cD + [-0.25, 0.1, 0], 0.72),
    Name("P", cP + [-0.03, -0.46, 0], 0.72)
]

Uses the shared definitions for this lesson's figures.

A rectangle has equal opposite sides. Reading the vertical pair gives the sine of the sum, and reading the horizontal pair gives the cosine.

The proof is the step-through figure with the two places checked where a drawing could mislead: that the corner angle at $B$ really is $\alpha$, and that the points sit on the sides of the rectangle in the order drawn.

What the picture does not cover

The picture needs both angles positive and their sum below 90°. Our opening example, 30° and 60°, already falls outside it: at a sum of exactly 90° the point $P$ lands on the corner $D$ and the triangle $ODP$ collapses. Beyond 90° the point $P$ moves to the left of $O$ and a different drawing is needed, and for angles such as 200° or $-40°$ there are no right triangles to stack at all.

Pause and try

Ignore that for a moment and put $\alpha = 30°$, $\beta = 60°$ into the sine formula. Does it give $\sin 90° = 1$? Then try $\alpha = 100°$, $\beta = 50°$ with a calculator, where the left side is $\sin 150° = 0.5$.

Show answerHide answer

For 30° and 60°: $\sin 30°\cos 60° + \cos 30°\sin 60° = \frac{1}{2}\cdot\frac{1}{2} + \frac{\sqrt 3}{2}\cdot\frac{\sqrt 3}{2} = \frac{1}{4} + \frac{3}{4} = 1$. For 100° and 50°: $\sin 100°\cos 50° \approx 0.9848 \times 0.6428 \approx 0.6330$ and $\cos 100°\sin 50° \approx -0.1736 \times 0.7660 \approx -0.1330$, which add to $0.5000$. The formula holds in both cases, and in the second one a negative cosine was needed to make it come out.

A check of the sine formula with \beta = 50° and \alpha running over a full turn. The dots are \sin\alpha\cos 50° + \cos\alpha\sin 50° computed every 20°, and they all land on the green curve \sin(\alpha + 50°), including where the picture proof says nothing.
A check of the sine formula with $\beta = 50°$ and $\alpha$ running over a full turn. The dots are $\sin\alpha\cos 50° + \cos\alpha\sin 50°$ computed every 20°, and they all land on the green curve $\sin(\alpha + 50°)$, including where the picture proof says nothing.
Monocurl source
let fw = Frame(0, 360, -1.3, 1.3, [0.15, -0.25, 0], 6.3, 3.0)
let BE = rad(50)
mesh grid = [
    GridLines(fw, [90, 180, 270, 360], [-1, -0.5, 0.5, 1]),
    Seg(at(fw, 0, 0), at(fw, 360, 0), SLATE, 2.5),
    Seg(at(fw, 0, -1.3), at(fw, 0, 1.3), SLATE, 2.5)
]
mesh ticks = block {
    . DegTicks(fw, [0, 90, 180, 270, 360])
    for (v in [-1, 0, 1]) {
        . Name(to_string(v), [fx(fw, 0) - 0.32, fy(fw, v), 0], 0.72, SLATE)
    }
    . Name("\alpha", [fx(fw, 360) + 0.3, fy(fw, 0), 0], 0.85, SLATE)
}
mesh curves = [
    Graph(fw, |d| sin(rad(d)) * cos(BE), 0, 360, AZURE, 3),
    Graph(fw, |d| cos(rad(d)) * sin(BE), 0, 360, EMBER, 3),
    Graph(fw, |d| sin(rad(d) + BE), 0, 360, LEAF, 5)
]
mesh dots = map(range(0, 19), |k| Pt(at(fw, 20 * k, sin(rad(20 * k)) * cos(BE) + cos(rad(20 * k)) * sin(BE)), INK, 0.075))
mesh legend = [
    Name("\sin\alpha\cos 50^\circ", [-2.1, 1.75, 0], 0.8, AZURE),
    Name("\cos\alpha\sin 50^\circ", [0.2, 1.75, 0], 0.8, EMBER),
    Name("\sin(\alpha + 50^\circ)", [2.45, 1.75, 0], 0.8, LEAF)
]

Uses the shared definitions for this lesson's figures.

Nineteen agreeing dots are evidence, and they are not a proof. To prove the formulas for every angle we need an argument that never mentions a triangle, and the unit circle definition offers one, because it speaks about rotations and rotations do not care what size the angles are.

A proof for every angle

Write $P(\theta)$ for the point $(\cos\theta, \sin\theta)$ of the unit circle. Take any two angles $\alpha$ and $\beta$ and join $P(\alpha)$ to $P(\beta)$ by a straight segment, a chord of the circle. Now turn the whole plane about the origin through $-\beta$. Turning through $\theta$ and then through $-\beta$ is the same as turning through $\theta - \beta$, so $P(\beta)$ goes to $P(0) = (1, 0)$ and $P(\alpha)$ goes to $P(\alpha - \beta)$.

The chord from $P(\alpha)$ to $P(\beta)$, here with $\alpha = 215°$ and $\beta = 75°$, is turned back through $\beta$. It arrives as the chord from $P(\alpha - \beta)$ to $P(0)$ with its length unchanged.
Monocurl source
let C = [-1.0, 0, 0]
let R = 1.85
let AL = rad(215)
let BE = rad(75)
let gap = |p, q| sqrt((p[0] - q[0]) ^ 2 + (p[1] - q[1]) ^ 2)
mesh base = [CircleAxes(C, R), ArcPath(C, R, 0, TAU)]
mesh ghost = [
    Seg(pol(C, R, AL), pol(C, R, BE), MIST, 5),
    Pt(pol(C, R, AL), SLATE, 0.08),
    Pt(pol(C, R, BE), SLATE, 0.08)
]
let Turn = |u| {
    let p = pol(C, R, AL - u * BE)
    let q = pol(C, R, BE - u * BE)
    return [
        Region([C, p, q], tint(EMBER, 0.16)),
        Seg(C, p, INK, 2.5),
        Seg(C, q, INK, 2.5),
        Seg(p, q, EMBER, 5),
        Pt(p, INK, 0.09),
        Pt(q, INK, 0.09)
    ]
}
mesh chord = Turn(u: 0)
mesh labels = [
    Name("P(\alpha)", C + (R + 0.5) * dir(AL), 0.78),
    Name("P(\beta)", pol(C, R, BE) + [0.6, 0.22, 0], 0.78),
    Name("P(\alpha-\beta)", pol(C, R, AL - BE) + [-0.6, 0.35, 0], 0.78),
    Name("P(0)", [C[0] + R + 0.5, 0.32, 0], 0.78),
    center{[2.6, 1.35, 0]} Note("chord before", 0.74),
    Name(to_string(gap(dir(AL), dir(BE)), 3), [2.6, 0.85, 0], 1.0, EMBER),
    center{[2.6, -0.35, 0]} Note("chord after", 0.74),
    Name(to_string(gap(dir(AL - BE), dir(0)), 3), [2.6, -0.85, 0], 1.0, EMBER)
]

slide "turn"
    play Wait(1)
    chord.u = 1
    play Lerp(3, [&chord])
    play Wait(2)

Uses the shared definitions for this lesson's figures.

A rotation moves every point but changes no distance, so the chord has the same length before and after. Both lengths can be computed from coordinates with the distance formula, which is the Pythagorean theorem: the squared distance between $(x_1, y_1)$ and $(x_2, y_2)$ is $(x_1 - x_2)^2 + (y_1 - y_2)^2$. Setting the two computations equal gives an equation relating $\cos(\alpha - \beta)$ to the sines and cosines of $\alpha$ and $\beta$. That is the plan. Here is the full statement and the computation.

So the stacked triangles told us what the formulas are and why the mixed products appear, and the chord shows that the same formulas hold for all angles, where no triangle is available. Nothing here depends on the unit of angle either: in radians the formulas read the same, with $\frac{\pi}{2}$ in place of 90° in the proof.

Doubling an angle

Pause and try

Put $\beta = \alpha$ in the formulas for $\sin(\alpha + \beta)$ and $\cos(\alpha+\beta)$. What do they say about $\sin 2\alpha$ and $\cos 2\alpha$? Check your formula for $\sin 2\alpha$ at $\alpha = 30°$.

Show answerHide answer

$\sin 2\alpha = \sin\alpha\cos\alpha + \cos\alpha\sin\alpha = 2\sin\alpha\cos\alpha$, and $\cos 2\alpha = \cos^2\alpha - \sin^2\alpha$. At 30°: $2 \cdot \frac{1}{2} \cdot \frac{\sqrt 3}{2} = \frac{\sqrt 3}{2}$, which is $\sin 60°$.

These are the double angle formulas. For the cosine, the Pythagorean identity lets us trade $\cos^2\alpha$ for $1 - \sin^2\alpha$ or the reverse, which gives three equivalent forms:

$$ \sin 2\alpha = 2\sin\alpha\cos\alpha, \qquad \cos 2\alpha = \cos^2\alpha - \sin^2\alpha = 1 - 2\sin^2\alpha = 2\cos^2\alpha - 1. $$

They hold for every $\alpha$, since they are special cases of the theorem. When $0° < 2\alpha < 180°$ the sine formula can also be seen directly, as two ways of measuring one area.

An isosceles triangle with two sides of length 1 and the angle 2\alpha between them, drawn twice. With a side of length 1 as base its height is \sin 2\alpha. Cut along the bisector, its base is 2\sin\alpha and its height is \cos\alpha. Equal areas give \sin 2\alpha = 2\sin\alpha\cos\alpha.
An isosceles triangle with two sides of length 1 and the angle $2\alpha$ between them, drawn twice. With a side of length 1 as base its height is $\sin 2\alpha$. Cut along the bisector, its base is $2\sin\alpha$ and its height is $\cos\alpha$. Equal areas give $\sin 2\alpha = 2\sin\alpha\cos\alpha$.
Monocurl source
let U = 3.0
let AL = rad(35)
let Tri = |o| [o, o + [U, 0, 0], o + U * dir(2 * AL)]
let O1 = [-3.55, -1.05, 0]
let O2 = [0.45, -1.05, 0]
let t1 = Tri(O1)
let t2 = Tri(O2)
let f1 = [t1[2][0], O1[1], 0]
let M = 0.5 * (t2[1] + t2[2])
let out = dir(AL)
mesh left = [
    Shape(t1, AZURE, 0.14, 3),
    Wedge(O1, 0, 2 * AL, SLATE, 0.55),
    Seg(f1, t1[2], EMBER, 5),
    Square90(f1, [1, 0, 0], [0, 1, 0], SLATE),
    Name("2\alpha", O1 + 0.95 * dir(AL), 0.76, SLATE),
    Name("1", 0.5 * (O1 + t1[1]) + [0.5, -0.27, 0], 0.8),
    Name("1", 0.5 * (O1 + t1[2]) + [-0.25, 0.1, 0], 0.8),
    Name("\sin 2\alpha", [f1[0] + 0.68, O1[1] + 0.75, 0], 0.76, EMBER),
    Name("\text{area} = \frac{1}{2}\cdot 1\cdot\sin 2\alpha", [-2.05, -1.8, 0], 0.76)
]
mesh right = [
    Shape(t2, AZURE, 0.14, 3),
    Wedge(O2, 0, AL, SLATE, 0.7),
    Wedge(O2, AL, AL, SLATE, 0.55),
    Seg(O2, M, EMBER, 5),
    Seg(t2[1], t2[2], PLUM, 5),
    Square90(M, -1 * out, dir(AL + PI / 2), INK),
    Name("\alpha", O2 + 1.0 * dir(AL / 2), 0.76, SLATE),
    Name("\alpha", O2 + 0.85 * dir(1.5 * AL), 0.76, SLATE),
    Name("\cos\alpha", O2 + 0.62 * (M - O2) + 0.3 * dir(AL + PI / 2), 0.76, EMBER),
    Name("\sin\alpha", 0.5 * (t2[1] + M) + 0.55 * out, 0.76, PLUM),
    Name("\sin\alpha", 0.5 * (t2[2] + M) + 0.55 * out, 0.76, PLUM),
    Name("\text{area} = \frac{1}{2}\cdot 2\sin\alpha\cdot\cos\alpha", [1.95, -1.8, 0], 0.76)
]

Uses the shared definitions for this lesson's figures.

On the left, the triangle stands on one of its unit sides. The far end of the other unit side is at height $\sin 2\alpha$, so the area is $\frac{1}{2}\sin 2\alpha$. On the right, the bisector of the angle $2\alpha$ cuts the triangle into two congruent right triangles, each with hypotenuse 1 and angle $\alpha$, so it meets the third side at a right angle. That side has length $2\sin\alpha$ and the bisector has length $\cos\alpha$, so the area is $\sin\alpha\cos\alpha$. It is one triangle, so $\frac{1}{2}\sin 2\alpha = \sin\alpha\cos\alpha$.

The graphs of 2\sin\theta and \sin 2\theta over one full turn. They differ in height and in how often they repeat, and agree only where both are zero. The dots are 2\sin\theta\cos\theta, which follows \sin 2\theta exactly.
The graphs of $2\sin\theta$ and $\sin 2\theta$ over one full turn. They differ in height and in how often they repeat, and agree only where both are zero. The dots are $2\sin\theta\cos\theta$, which follows $\sin 2\theta$ exactly.
Monocurl source
let fw = Frame(0, 360, -2.3, 2.3, [0.15, -0.25, 0], 6.3, 3.0)
mesh grid = [
    GridLines(fw, [90, 180, 270, 360], [-2, -1, 1, 2]),
    Seg(at(fw, 0, 0), at(fw, 360, 0), SLATE, 2.5),
    Seg(at(fw, 0, -2.3), at(fw, 0, 2.3), SLATE, 2.5)
]
mesh ticks = block {
    . DegTicks(fw, [0, 90, 180, 270, 360])
    for (v in [-2, -1, 0, 1, 2]) {
        . Name(to_string(v), [fx(fw, 0) - 0.32, fy(fw, v), 0], 0.72, SLATE)
    }
    . Name("\theta", [fx(fw, 360) + 0.3, fy(fw, 0), 0], 0.85, SLATE)
}
mesh curves = [
    Graph(fw, |d| 2 * sin(rad(d)), 0, 360, ROSE, 4),
    Graph(fw, |d| sin(rad(2 * d)), 0, 360, AZURE, 4)
]
mesh dots = map(range(0, 25), |k| Pt(at(fw, 15 * k, 2 * sin(rad(15 * k)) * cos(rad(15 * k))), INK, 0.07))
mesh legend = [
    Name("2\sin\theta", [-2.0, 1.75, 0], 0.8, ROSE),
    Name("\sin 2\theta", [0.1, 1.75, 0], 0.8, AZURE),
    Pt([1.7, 1.75, 0], INK, 0.07),
    Name("2\sin\theta\cos\theta", [2.75, 1.75, 0], 0.8)
]

Uses the shared definitions for this lesson's figures.

Worked examples

Looking back

The obstacle was that the point at angle $\alpha + \beta$ is described most simply in a tilted frame, as so far along the $\alpha$ direction and so far across it, while sine and cosine ask for its vertical and horizontal position. The stacked triangles resolve each tilted piece into vertical and horizontal parts, which is where the four products come from. That handled acute angles, and the proof for all angles came from giving up triangles and using the one property of a rotation that holds everywhere, that it preserves distance.

Later lessons lean on these formulas. The symmetry rule $\sin(180° - \theta) = \sin\theta$ that they contain is what produces two answers in the ambiguous case of the law of sines, and in the lesson on sine waves they explain why two waves of the same frequency add up to a single wave of that frequency.

Check yourself

Without a calculator: what is $\sin 20°\cos 40° + \cos 20°\sin 40°$?

Problems

The first three repeat the lesson’s moves, the next three combine them, and the last two extend the lesson.

Problem 1

Find $\cos 15°$ and $\sin 105°$ exactly. Explain without computing why they had to come out equal.

Show solutionHide solution

$\cos 15° = \cos(45° - 30°) = \cos 45°\cos 30° + \sin 45°\sin 30° = \frac{\sqrt 2}{2}\cdot\frac{\sqrt 3}{2} + \frac{\sqrt 2}{2}\cdot\frac{1}{2} = \frac{\sqrt 6 + \sqrt 2}{4} \approx 0.9659$. And $\sin 105° = \sin(60° + 45°) = \sin 60°\cos 45° + \cos 60°\sin 45° = \frac{\sqrt 3}{2}\cdot\frac{\sqrt 2}{2} + \frac{1}{2}\cdot\frac{\sqrt 2}{2} = \frac{\sqrt 6 + \sqrt 2}{4}$. They are equal because $\sin 105° = \sin(180° - 105°) = \sin 75°$ by the mirror symmetry of the circle in the $y$ axis, and $\sin 75° = \cos(90° - 75°) = \cos 15°$ by the complement rule. Both equal the $\sin 75°$ of the lesson.

Problem 2

An acute angle $\theta$ has $\sin\theta = \frac{3}{5}$. Find $\sin 2\theta$ and $\cos 2\theta$. In which quadrant is the point at angle $2\theta$?

Show solutionHide solution

Since $\theta$ is acute, $\cos\theta = +\sqrt{1 - \frac{9}{25}} = \frac{4}{5}$. Then $\sin 2\theta = 2\cdot\frac{3}{5}\cdot\frac{4}{5} = \frac{24}{25}$ and $\cos 2\theta = \frac{16}{25} - \frac{9}{25} = \frac{7}{25}$. Both are positive, so the point at $2\theta$ is in the first quadrant. Check: $24^2 + 7^2 = 576 + 49 = 625 = 25^2$.

Problem 3

Use the addition and subtraction formulas to recover two symmetry rules of the unit circle: $\sin(\theta + 90°) = \cos\theta$ and $\sin(180° - \theta) = \sin\theta$.

Show solutionHide solution

$\sin(\theta + 90°) = \sin\theta\cos 90° + \cos\theta\sin 90° = \sin\theta\cdot 0 + \cos\theta\cdot 1 = \cos\theta$. And $\sin(180° - \theta) = \sin 180°\cos\theta - \cos 180°\sin\theta = 0\cdot\cos\theta - (-1)\sin\theta = \sin\theta$. The general formulas contain the symmetry rules as the special cases where one angle is a multiple of 90°.

Problem 4

A student computes $\cos 75° = \cos 45°\cos 30° + \sin 45°\sin 30° \approx 0.966$. Without redoing the calculation, explain how you can tell the answer is wrong, then say what the student actually computed.

Show solutionHide solution

As the angle goes from 0° to 90° the point of the unit circle moves steadily to the left, so the cosine decreases. Since $75° > 45°$, $\cos 75°$ must be less than $\cos 45° \approx 0.707$, and $0.966$ is not. The expression with a plus sign between like products is the cosine of a difference: the student computed $\cos(45° - 30°) = \cos 15°$. The correct value is $\cos 45°\cos 30° - \sin 45°\sin 30° \approx 0.6124 - 0.3536 = 0.2588$.

Problem 5

One line through the origin has slope $\frac{1}{2}$ and another has slope $\frac{1}{3}$. Let $\alpha$ and $\beta$ be the angles they make with the positive $x$ axis, so that $\tan\alpha = \frac{1}{2}$ and $\tan\beta = \frac{1}{3}$. Show that $\alpha + \beta$ is exactly 45°.

Show solutionHide solution

By the tangent formula, $\tan(\alpha+\beta) = \dfrac{\frac{1}{2} + \frac{1}{3}}{1 - \frac{1}{2}\cdot\frac{1}{3}} = \dfrac{5/6}{5/6} = 1$. Both slopes are positive and less than 1, so $\alpha$ and $\beta$ are each between 0° and 45°, and their sum is between 0° and 90°. The only angle in that range with tangent 1 is 45°. (A calculator gives $\alpha \approx 26.565°$ and $\beta \approx 18.435°$.) The range check matters: $\tan 225° = 1$ as well, so the value of the tangent alone does not determine the angle.

Problem 6

Prove that $\sin 3\theta = 3\sin\theta - 4\sin^3\theta$ for every $\theta$, and check it at $\theta = 30°$.

Show solutionHide solution

Write $3\theta = 2\theta + \theta$. Then $\sin 3\theta = \sin 2\theta\cos\theta + \cos 2\theta\sin\theta$. Use $\sin 2\theta = 2\sin\theta\cos\theta$ and the form $\cos 2\theta = 1 - 2\sin^2\theta$, chosen because the target contains only sines:

$$ \sin 3\theta = 2\sin\theta\cos^2\theta + (1 - 2\sin^2\theta)\sin\theta. $$

Replace $\cos^2\theta$ by $1 - \sin^2\theta$: $\sin 3\theta = 2\sin\theta - 2\sin^3\theta + \sin\theta - 2\sin^3\theta = 3\sin\theta - 4\sin^3\theta$. At 30°: $3\cdot\frac{1}{2} - 4\cdot\frac{1}{8} = \frac{3}{2} - \frac{1}{2} = 1 = \sin 90°$.

Problem 7

The picture proof covers only $\alpha, \beta > 0°$ with $\alpha + \beta < 90°$. Here is one step of a way to extend it without the chord. Suppose both sum formulas are known to hold for a particular pair $(\alpha, \beta)$. Using only the quarter turn rules $\sin(\theta + 90°) = \cos\theta$ and $\cos(\theta + 90°) = -\sin\theta$, show that both formulas then hold for the pair $(\alpha + 90°, \beta)$.

Show solutionHide solution

Write $\alpha' = \alpha + 90°$, so $\sin\alpha' = \cos\alpha$ and $\cos\alpha' = -\sin\alpha$. For the sine: $\sin(\alpha' + \beta) = \sin\bigl((\alpha+\beta) + 90°\bigr) = \cos(\alpha+\beta)$, which by assumption is $\cos\alpha\cos\beta - \sin\alpha\sin\beta = \sin\alpha'\cos\beta + \cos\alpha'\sin\beta$. That is the sine formula for $(\alpha', \beta)$. For the cosine: $\cos(\alpha'+\beta) = \cos\bigl((\alpha+\beta) + 90°\bigr) = -\sin(\alpha+\beta) = -\sin\alpha\cos\beta - \cos\alpha\sin\beta = \cos\alpha'\cos\beta - \sin\alpha'\sin\beta$, the cosine formula for $(\alpha', \beta)$. The same works for $\beta$, and for subtracting 90°, so once the formulas are known for all $\alpha$ and $\beta$ from 0° up to 90° they spread to every pair of angles. Getting that whole square of angles from the picture still takes separate arguments for the cases $\alpha + \beta \ge 90°$ and for an angle equal to 0°, which is why the lesson used the chord.

Problem 8

(a) Show that $\sin(\alpha+\beta) + \sin(\alpha-\beta) = 2\sin\alpha\cos\beta$. (b) By choosing $\alpha$ and $\beta$ suitably, deduce that for all $x$ and $y$

$$ \sin x + \sin y = 2\sin\frac{x+y}{2}\cos\frac{x-y}{2}. $$

(c) Check (b) with $x = 90°$ and $y = 30°$.

Show solutionHide solution

(a) Add the two formulas $\sin(\alpha+\beta) = \sin\alpha\cos\beta + \cos\alpha\sin\beta$ and $\sin(\alpha-\beta) = \sin\alpha\cos\beta - \cos\alpha\sin\beta$. The terms $\cos\alpha\sin\beta$ cancel and $2\sin\alpha\cos\beta$ remains. (b) We want $\alpha + \beta = x$ and $\alpha - \beta = y$. Adding these gives $\alpha = \frac{x+y}{2}$ and subtracting gives $\beta = \frac{x-y}{2}$. Substituting into (a) gives the stated formula. (c) The left side is $1 + \frac{1}{2} = \frac{3}{2}$. The right side is $2\sin 60°\cos 30° = 2\cdot\frac{\sqrt 3}{2}\cdot\frac{\sqrt 3}{2} = \frac{3}{2}$. This “sum to product” formula returns in the lesson on sine waves, where it explains the beating of two notes that are slightly out of tune.

Shared definitions used by this lesson's figures
# house kit: prepended to every essay figure after the std imports

# palette. AZURE and EMBER are the two actors, LEAF marks an answer, ROSE a problem
let PAPER = [1, 1, 1, 1]
let INK = [0.08, 0.09, 0.11, 1]
let SLATE = [0.40, 0.44, 0.50, 1]
let MIST = [0.85, 0.86, 0.88, 1]
let AZURE = [0.13, 0.42, 0.68, 1]
let EMBER = [0.89, 0.42, 0.12, 1]
let LEAF = [0.13, 0.55, 0.36, 1]
let ROSE = [0.78, 0.22, 0.24, 1]
let PLUM = [0.47, 0.30, 0.66, 1]
let GOLD = [0.85, 0.62, 0.08, 1]

background = PAPER

# an opaque pastel of col; use for fills so overlaps do not darken
let tint = |col, amount = 0.18| [
    1 - (1 - col[0]) * amount,
    1 - (1 - col[1]) * amount,
    1 - (1 - col[2]) * amount,
    1
]

# text: Cap for labels, Note for quiet labels, Math for typeset maths
let Cap = |txt, size = 0.75, col = INK| color{col} Text(txt, size, "Charter")
let Note = |txt, size = 0.65| color{SLATE} Text(txt, size, "Charter")
let Math = |tex, size = 0.9, col = INK| color{col} Tex(tex, size)

# a paper rectangle to sit behind text that crosses lines
let Plate = |pos, size| center{pos} fill{PAPER} stroke{alpha{0} PAPER, 0} Rect(size)

# shapes with fill and stroke both set, so no stray black appears
let Pt = |pos, col = INK, r = 0.075| center{pos} fill{col} stroke{col, 1} Circle(r, 48)
let Ring = |pos, col = INK, r = 0.1, w = 3| center{pos} fill{PAPER} stroke{col, w} Circle(r, 48)
let Seg = |a, b, col = INK, w = 3| stroke{col, w} Line(a, b)
let Path = |pts, col = INK, w = 3| stroke{col, w} Polyline(pts)
let Region = |pts, col| fill{col} stroke{alpha{0} col, 0} Polygon(pts)
let Outline = |pts, col = INK, w = 3| fill{alpha{0} PAPER} stroke{col, w} Polygon(pts)
let Shape = |pts, col, amount = 0.18, w = 3| fill{tint(col, amount)} stroke{col, w} Polygon(pts)
let Disc = |pos, r, col, amount = 0.18, w = 3| center{pos} fill{tint(col, amount)} stroke{col, w} Circle(r, 96)
let Box = |pos, size, col, amount = 0.18, w = 3| center{pos} fill{tint(col, amount)} stroke{col, w} Rect(size)
# a line from a to b ending in a small solid arrowhead
let Tip = |a, b, col = INK, w = 3, head = 0.17| {
    let d = normalize([b[0] - a[0], b[1] - a[1], 0])
    let n = [-d[1], d[0], 0]
    let base = [b[0] - d[0] * head, b[1] - d[1] * head, 0]
    return [
        stroke{col, w} Line(a, base),
        fill{col} stroke{col, 1} Polygon([
            b,
            [base[0] + n[0] * head * 0.42, base[1] + n[1] * head * 0.42, 0],
            [base[0] - n[0] * head * 0.42, base[1] - n[1] * head * 0.42, 0]
        ])
    ]
}

# plots. a frame is [x0, x1, y0, y1, centre, width, height]: a data window and the scene box it fills
let Frame = |x0, x1, y0, y1, c = [0, 0, 0], w = 6.4, h = 3.6| [x0, x1, y0, y1, c, w, h]
let fx = |fr, x| fr[4][0] + (x - (fr[0] + fr[1]) / 2) * fr[5] / (fr[1] - fr[0])
let fy = |fr, y| fr[4][1] + (y - (fr[2] + fr[3]) / 2) * fr[6] / (fr[3] - fr[2])
let at = |fr, x, y| [fx(fr, x), fy(fr, y), 0]

# axes through the data origin (or the nearest edge of the window), with optional names
let Axes = |fr, x_name = "", y_name = "", col = SLATE| {
    let ox = clamp(fr[0], 0, fr[1])
    let oy = clamp(fr[2], 0, fr[3])
    var parts = [
        Tip(at(fr, fr[0], oy), [fx(fr, fr[1]) + 0.25, fy(fr, oy), 0], col, 2.5, 0.15),
        Tip(at(fr, ox, fr[2]), [fx(fr, ox), fy(fr, fr[3]) + 0.25, 0], col, 2.5, 0.15)
    ]
    if (x_name != "") {
        parts .= center{[fx(fr, fr[1]) + 0.5, fy(fr, oy), 0]} Math(x_name, 0.8, col)
    }
    if (y_name != "") {
        parts .= center{[fx(fr, ox), fy(fr, fr[3]) + 0.5, 0]} Math(y_name, 0.8, col)
    }
    return parts
}

# tick marks with numbers at the listed data values
let XTicks = |fr, xs, col = SLATE, places = 0| block {
    let oy = fy(fr, clamp(fr[2], 0, fr[3]))
    for (x in xs) {
        . Seg([fx(fr, x), oy - 0.07, 0], [fx(fr, x), oy + 0.07, 0], col, 2)
        . center{[fx(fr, x), oy - 0.33, 0]} Math(to_string(x, places), 0.72, col)
    }
}
let YTicks = |fr, ys, col = SLATE, places = 0| block {
    let ox = fx(fr, clamp(fr[0], 0, fr[1]))
    for (y in ys) {
        . Seg([ox - 0.07, fy(fr, y), 0], [ox + 0.07, fy(fr, y), 0], col, 2)
        . center{[ox - 0.36, fy(fr, y), 0]} Math(to_string(y, places), 0.72, col)
    }
}

# faint grid lines at the listed data values
let GridLines = |fr, xs, ys, col = MIST| block {
    for (x in xs) {
        . Seg(at(fr, x, fr[2]), at(fr, x, fr[3]), col, 1.5)
    }
    for (y in ys) {
        . Seg(at(fr, fr[0], y), at(fr, fr[1], y), col, 1.5)
    }
}

# the graph of f over [a, b], drawn in the frame. values are clamped to the window
let Graph = |fr, f, a, b, col = AZURE, w = 4, n = 160|
    stroke{col, w} Polyline(map(sample(a, b, n), |x| at(fr, x, clamp(fr[2], f(x), fr[3]))))

# the region between the graph of f and the x axis over [a, b]
let AreaUnder = |fr, f, a, b, col = AZURE, amount = 0.2, n = 80| {
    var pts = [at(fr, a, 0)]
    for (x in sample(a, b, n)) {
        pts .= at(fr, x, clamp(fr[2], f(x), fr[3]))
    }
    pts .= at(fr, b, 0)
    return fill{tint(col, amount)} stroke{alpha{0} col, 0} Polygon(pts)
}

let dir = |th| [cos(th), sin(th), 0]
let rad = |d| d * PI / 180
let pol = |o, r, t| [o[0] + r * cos(t), o[1] + r * sin(t), 0]
let ArcPath = |o, r, a, b, col = SLATE, w = 2.5| {
    let n = to_int(max(2, min(120, ceil(abs(b - a) * r / 0.05))))
    return stroke{col, w} Polyline(map(sample(a, b, n), |t| pol(o, r, t)))
}
let Wedge = |p, t0, sw, col, r = 0.5| fill{tint(col, 0.5)} stroke{col, 2.5} Polygon(block {
    . p
    for (t in sample(t0, t0 + sw, to_int(max(8, ceil(abs(sw) * 14))))) {
        . p + r * dir(t)
    }
})
let Name = |txt, pos, size = 0.8, col = INK| center{pos} Math(txt, size, col)
let Square90 = |p, s, t, col = INK, k = 0.2| stroke{col, 2.5} Polyline([p + k * s, p + k * s + k * t, p + k * t])
let CircleAxes = |o, r| [
    Seg([o[0] - r - 0.3, o[1], 0], [o[0] + r + 0.3, o[1], 0], SLATE, 2),
    Seg([o[0], o[1] - r - 0.2, 0], [o[0], o[1] + r + 0.2, 0], SLATE, 2),
    Name("1", [o[0] + r + 0.17, o[1] - 0.27, 0], 0.72, SLATE),
    Name("-1", [o[0] - r - 0.2, o[1] - 0.27, 0], 0.72, SLATE),
    Name("1", [o[0] - 0.2, o[1] + r + 0.17, 0], 0.72, SLATE)
]
let DegTicks = |fr, ds, col = SLATE| block {
    for (d in ds) {
        . center{[fx(fr, d), fy(fr, fr[2]) - 0.3, 0]} Math(str_join([to_string(d), "^\circ"]), 0.72, col)
    }
}
# two right triangles stacked at o: the lower has angle a, the upper has angle b and hypotenuse u
# returns the corners O, A, B, P, C, D of the rectangle drawn around them
let Stack = |o, u, a, b| {
    let w = u * cos(a) * cos(b)
    let h = u * sin(a + b)
    return [
        o,
        o + [w, 0, 0],
        o + [w, u * sin(a) * cos(b), 0],
        o + [u * cos(a + b), h, 0],
        o + [w, h, 0],
        o + [0, h, 0]
    ]
}
let StackFills = |s| [
    Region([s[0], s[1], s[2]], tint(SLATE, 0.28)),
    Region([s[0], s[2], s[3]], tint(SLATE, 0.1)),
    Region([s[2], s[4], s[3]], tint(SLATE, 0.28))
]
let StackLines = |s| [
    Outline([s[0], s[1], s[4], s[5]], SLATE, 2),
    Outline([s[0], s[2], s[3]], INK, 3),
    Seg(s[5], s[0], LEAF, 6),
    Seg(s[1], s[2], AZURE, 6),
    Seg(s[2], s[4], EMBER, 6),
    Pt(s[3], INK, 0.09)
]