p5.js Beginner Article 9

Trigonometry for Creative Coding

sin(), cos(), and angles — the mathematics behind circular, wave, and orbital motion.

⏱ 20 min read trigonometry sin cos radians circles waves

Why trigonometry matters here

You don’t need to understand the unit circle theoretically to use trigonometry in creative coding. You need two facts:

  1. cos(angle) gives the x component of a point on a circle
  2. sin(angle) gives the y component of a point on a circle

Everything else — waves, orbits, oscillations, spirals, lissajous figures — follows from these two facts.

Radians vs degrees

p5.js uses radians by default. One full circle = TWO_PI ≈ 6.28.

Degrees Radians p5.js constant
0 0
90° π/2 HALF_PI
180° π PI
270° 3π/2 PI + HALF_PI
360° TWO_PI

Convert between them:

let r = radians(90);   // degrees → radians → 1.5708
let d = degrees(PI);   // radians → degrees → 180

If you prefer to work in degrees, switch the angle mode:

function setup() {
  angleMode(DEGREES);  // now all trig functions expect degrees
}

Points on a circle

The most-used pattern in creative coding:

let x = cx + cos(angle) * radius;
let y = cy + sin(angle) * radius;

Where cx, cy is the centre. Use this to place any number of things evenly around a circle:

function setup() {
  createCanvas(500, 500);
  noLoop();
}

function draw() {
  background(20);
  translate(width / 2, height / 2);

  let count = 12;
  let radius = 180;

  for (let i = 0; i < count; i++) {
    let angle = TWO_PI / count * i;
    let x = cos(angle) * radius;
    let y = sin(angle) * radius;

    fill(200, 100, 255);
    noStroke();
    circle(x, y, 20);

    // Label with index
    fill(255);
    textAlign(CENTER, CENTER);
    textSize(10);
    text(i, x, y);
  }
}

Oscillation with sin()

sin() returns values between -1 and +1, cycling smoothly. Map it to any range:

function draw() {
  background(20);

  let t = frameCount * 0.04;

  // Horizontal oscillation
  let x = map(sin(t), -1, 1, 100, 500);
  fill(100, 200, 255);
  noStroke();
  circle(x, height / 2, 40);
}

Amplitude and frequency:

let amplitude  = 100;   // how far it moves (half the range)
let frequency  = 0.05;  // how fast it oscillates
let offset     = height / 2;  // centre point

let y = offset + sin(frameCount * frequency) * amplitude;

Multiple objects with phase offsets create wave patterns:

function draw() {
  background(20);
  noStroke();
  fill(100, 200, 255, 150);

  let n = 20;
  for (let i = 0; i < n; i++) {
    let x     = map(i, 0, n - 1, 50, width - 50);
    let phase = TWO_PI / n * i;
    let y     = height / 2 + sin(frameCount * 0.05 + phase) * 80;
    circle(x, y, 24);
  }
}

Lissajous curves

Combine sin and cos with different frequencies for complex orbital patterns:

function setup() {
  createCanvas(500, 500);
  background(20);
  stroke(100, 200, 255, 60);
  strokeWeight(1.5);
  noFill();
}

function draw() {
  translate(width / 2, height / 2);

  let a = 3, b = 2;  // frequency ratio
  let delta = frameCount * 0.005;  // phase shift

  beginShape();
  for (let t = 0; t < TWO_PI; t += 0.02) {
    let x = 200 * sin(a * t + delta);
    let y = 200 * cos(b * t);
    vertex(x, y);
  }
  endShape(CLOSE);
}

Polar to cartesian — spirograph

function setup() {
  createCanvas(600, 600);
  background(20);
  noFill();
  stroke(255, 100, 200, 80);
  strokeWeight(1);

  translate(width / 2, height / 2);

  let R = 150, r = 80, d = 120;

  beginShape();
  for (let t = 0; t < TWO_PI * 12; t += 0.02) {
    let x = (R - r) * cos(t) + d * cos((R - r) / r * t);
    let y = (R - r) * sin(t) - d * sin((R - r) / r * t);
    vertex(x, y);
  }
  endShape();
}

atan2() — angle from position

atan2(y, x) returns the angle (in radians) from the origin to point (x, y). Essential for making things face toward a target:

function draw() {
  background(20);

  let angle = atan2(mouseY - height / 2, mouseX - width / 2);

  push();
  translate(width / 2, height / 2);
  rotate(angle);
  fill(255, 100, 50);
  noStroke();
  triangle(30, 0, -20, -12, -20, 12);  // arrow pointing right before rotate
  pop();
}

Key takeaways

  • cos(angle) → x component; sin(angle) → y component of a unit circle
  • p5.js uses radians by default; TWO_PI = full circle; use angleMode(DEGREES) to switch
  • x = cx + cos(a) * r, y = cy + sin(a) * r places a point on a circle
  • sin(frameCount * freq) oscillates between -1 and +1 — multiply by amplitude to scale
  • Phase offsets (sin(t + phaseOffset)) shift when the cycle starts — great for wave patterns
  • atan2(dy, dx) converts a direction vector to an angle