cos and sin

cos\cos and sin\sin are what you use when you want to draw a circle, or animate something moving in a circle.

In the post on unit vectors, we defined the unit nn-sphere and found a parametrization of it. In the previous post, we encountered the arclength parametrization, which for most purposes is more convenient than other parametrizations. In this post, we discuss the arclength parametrization of the unit circle S1,\cl{S}{S^1}, which is given by the cos\cos and sin\sin functions. This will be useful for drawing things along a circle or animating something that's rotating.

Radians

We need to start by naming the length of the unit circle.

By scaling, it follows that the circumference of a circle of radius rr is given by ϖr.\Twopi r. For example, the circumference of a circle of radius 44 is 4ϖ25.13.4\Twopi\approx25.13\dots. Equivalently, we could define ϖ\Twopi as the ratio of any circle's circumference to its radius.

cos and sin

Here's a graph to experiment with that.

This completely defines cos\cos and sin,\sin, but doesn't tell us how to compute them. However, in just a few posts we'll derive an algorithm to compute these.

You may be used to a definition of cos\cos and sin\sin in terms of ratios of right-angle triangles; I'll review that interpretation below. That interpretation is often useful, but it doesn't work for angles larger than 90.90^\circ.

Animation

Let's say more about animation. So far, cos\cos and sin\sin tell us how to parametrize the unit circle, i.e. the circle of radius 11 centered at the origin O=(0,0).\O=(0,0). What about other circles?

Let's do this in steps. First, let

S1(r)={vR2:v=r}\cl S{S^1}(r) = \{v\in\VecR2 \mathrel: \|v\| = r \}

be the circle of radius rr centered at the origin. We have

S1(r):=rS1:={rvvS1}\begin{align*} \cl S{S^1}(r) &\phantom:= r \cl S{S^1}\\ &\Defeq \{rv \mid v\in \cl S{S^1}\} \end{align*}

so can parametrize this by

r(cost,sint)=(rcost,rsint),t[0,ϖ].r\ivec{\cos t,\sin t} = \ivec{r\cos t,r\sin t}, \qquad t\in\CIntvl0\twopi.

Next, let's consider circles centered at other points. For a point PA2P\in\AffR2 and a radius r>0,r>0, let

S1(P,r):={QA2 ⁣:PQ=r}\cl S{S^1}(P,r) \Defeq \{Q \in \AffR2 \colon \|P-Q\| = r \}

be the circle of radius rr centered at P.P. Note that S1(r)\cl S{S^1}(r) lives in the vector space R2,\VecR2, while S1(P,r)\cl S{S^1}(P,r) lives in the affine space A2;\AffR2; recall that "the origin" is a feature of vector space. We can get S1(P,r)\cl S{S^1}(P, r) by translating S1(r)\cl S{S^1}(r) to the point P:P: in other words,

S1(P,r):=P+S1(r):={P+vvS1(r)}\begin{align*} \cl S{S^1}(P, r) &\phantom:= P + \cl S{S^1}(r)\\ &\Defeq \{P + v \mid v \in \cl S{S^1}(r) \} \end{align*}

Therefore, a parametrization of S1(P,r)\cl S{S^1}(P, r) is given by

P+r(cost,sint)=p1+rcost,p2+rsint,t[0,ϖ],P+r\ivec{\cos t,\sin t} = \ipt{\cl{p}{p_1}+r\cos t,\cl{p}{p_2}+r\sin t}, \qquad t\in\CIntvl0\twopi,

where P=p1,p2.P=\ipt{\cl{p}{p_1},\cl{p}{p_2}}.

That does everything we need if we want to draw a circle (or a part of a circle). Often though, we want to animate something moving in a circle. This means we need to further take into account its speed (or angular velocity) and its starting position.

By default, (cost,sint)(\cos t,\sin t) starts at (1,0)(1,0) and goes around the circle one every ϖ\Twopi units of time, going counterclockwise. To have it start at a different angle θ0,\cl{θ}{\theta_0}, we just add that on to t:t:

(cos(θ0+t),sin(θ0+t))\ivec{\cos(\cl{θ}{\theta_0} + t), \sin(\cl{θ}{\theta_0} + t)}

To change the speed, we multiply tt by a constant. For example, if tt is measured in seconds and we want to go around the circle once every two seconds, we'd use

(cos(θ0+ϖt2),sin(θ0+ϖt2))\ivec{\cos\left(\cl{θ}{\theta_0}+\frac{\Twopi t}2\right), \sin\left(\cl{θ}{\theta_0}+\frac{\Twopi t}2\right)}

since going around the circle once every two seconds is the same as going halfway around the circle every second. We can also scale by a negative number to go clockwise instead of counter-clockwise.

To summarize:

Here's some code to try that out:

import { useEffect, useRef } from "react";

/** Point in 2-dimensional (affine) space */
type Pt2 = [number, number];

type Circle = {
  /** Measured in [0, 100] x [0, 100] and mapped onto the actual screen later */
  center: Pt2;
  radius: number;
};

const SECONDS = 1000,
  MINUTES = 60 * SECONDS;

const CIRCLE = 2 * Math.PI;

// scene setup
export default function Graph() {
  const items: MovingProps[] = [
    {
      children: "👻",
      circle: {
        center: [50, 50],
        radius: 25,
      },
      rpm: 45,
    },
    {
      children: "😈",
      circle: {
        center: [25, 25],
        radius: 20,
      },
      // negative to go clockwise
      rpm: -30,
    },
    {
      children: "😍",
      circle: {
        center: [75, 40],
        radius: 10,
      },
      rpm: 15,
    },
    {
      children: "😡",
      circle: {
        center: [30, 60],
        radius: 20,
      },
      rpm: -60,
    },
    {
      children: "😡",
      circle: {
        center: [70, 60],
        radius: 20,
      },
      // so it will collide with the other one
      initialAngle: CIRCLE / 2,
      rpm: 60,
    },
  ];

  return (
    <main>
      {items.map((props) => (
        <Moving key={JSON.stringify(props)} {...props} />
      ))}
    </main>
  );
}

// moving divs
interface MovingProps {
  children: React.ReactNode;
  circle: Circle;

  /**
   * Initial angle.
   * @default 0
   */
  initialAngle?: number;
  
  /** Revolutions per minute. */
  rpm: number;
}

function Moving({ children, circle, initialAngle = 0, rpm }: MovingProp) {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const start = performance.now();

    let cancelId: number;

    // update handler
    const update = (t: number) => {
      if (!ref.current) return;

      const angle = initialAngle + ((CIRCLE * (t - start)) / MINUTES) * rpm;
      const [x, y] = toScreenCoords(getCoords(circle, angle));

      // we want to position the center rather than the top-left corner
      const rect = ref.current.getBoundingClientRect();
      ref.current.style.transform = `translate(
        ${x - rect.width / 2}px,
        ${y - rect.height / 2}px
      )`;

      cancelId = requestAnimationFrame(update);
    };

    // start animation loop
    cancelId = requestAnimationFrame(update);

    // cancel animation loop on unmount
    return () => cancelAnimationFrame(cancelId);
  }, []);

  return (
    <div className="w-min text-3xl absolute" ref={ref}>
      {children}
    </div>
  );
}

/** Get the coordinates of a point on a circle. */
function getCoords(circle: Circle, angle: number): Pt2 {
  const {
    center: [cx, cy],
    radius: r,
  } = circle;

  // we subtract in the second coordinate because screen y-axis
  // goes down while math y-axis goes up
  return [cx + r * Math.cos(angle), cy - r * Math.sin(angle)];
}

/** Map [0, 100] x [0, 100] onto the actual screen dimensions */
function toScreenCoords([x, y]: Pt2) {
  return [(window.innerWidth * x) / 100, (window.innerHeight * y) / 100];
}

Triangle interpretation

You may be more used to a definition of cos\cos and sin\sin in terms of ratios of angles of right-angle triangles.

θ opposite adjacent hypotenuse

This perspective is often useful, but it doesn't work for angles larger than 90.90^\circ.

How does this relate to coordinates of points on the unit circle? First, note that for any point on a circle, we can form a right-angle triangle whose hypotenuse is the line segment from the center of the circle to that point (see below). In the case of the unit circle,

Special angles

Although we don't yet have a general algorithm for computing cos\cos and sin,\sin, we can read off the values of the coordinates of East (0),(0), North (90=ϖ4),(90^\circ=\frac\Twopi4), West (180=ϖ2),(180^\circ=\frac\Twopi2), and South (270=3ϖ4).(270^\circ=\frac{3\Twopi}4).

(cos0,sin0)=(1,0)(cosϖ2,sinϖ2)=(1,0)(cosϖ4,sinϖ4)=(0,1)(cos3ϖ4,sin3ϖ4)=(0,1)\begin{align*} \ivec{\cos0,\,\sin0} &= \ivec{1,0} & \ivec{\cos\frac\Twopi2,\,\sin\frac\Twopi2} &= \ivec{-1,\, 0}\\[.5em] \ivec{\cos\frac\Twopi4,\,\sin\frac\Twopi4} &= \ivec{0,\, 1} & \ivec{\cos\frac{3\Twopi}4,\,\sin\frac{3\Twopi}4} &= \ivec{0,\, -1} \end{align*}

With a bit more work, we can calculate the coordinates of the point at 45,45^\circ, using the triangle interpretation above.

45° x x 1

Since the angles of a triangle add up to 180=ϖ/2,180^\circ=\Twopi/2, the remaining angle is also 45=ϖ/8.45^\circ=\Twopi/8. This implies that the two side lengths cos45\cos45^\circ and sin45\sin45^\circ are equal; let's call that x.x. By the Pythagorean theorem,

cos(45)2+sin(45)2=12x2=1x2=12x=12=12=22\begin{align*} \cos(45^\circ)^2 + \sin(45^\circ)^2 &= 1\\ 2x^2 &= 1\\ x^2 &= \frac12\\ x &= \sqrt{\frac12}\\ &= \frac1{\sqrt2}\\ &= \frac{\sqrt2}2 \end{align*}

Therefore, we have

cos45=sin45=22.\cos45^\circ = \sin45^\circ = \frac{\sqrt2}2.