Drawing with trigonometry, polar coordinates, and curves
Learning objectives
- learner can map sine/cosine and polar coordinates to draw waves, circles, spirals, and rotational patterns
- learner can build custom vector paths with beginShape, vertex, and bezier curves
- learner can shape periodic motion with custom sine/tan/pow functions and noise perturbation
Capstone — one whole task that evidences the objectives
Create a generative geometric plotter piece — a family of spirals, Lissajous curves, and wave-clock forms built entirely from polar/trig math and custom shaping functions, with at least one form driven by a tan() wave so its radius spikes toward an extreme and wraps back from the other side.
Prerequisite modules
This module builds toward the classic pen-plotter aesthetic: mathematically pure line work — spirals, Lissajous knots, wave-clock mandalas — that reads as hand-drawn precision. In a live-visuals or print-edition practice, these forms are the workhorse vocabulary: they render fast, and pair naturally with slow ambient sets or gallery editions where crisp geometry matters more than pixel effects.
The arc starts supported: draw a single sine wave by mapping sin(angle) to canvas y-coordinates, then internalize the unit-circle move — cos for x, sin for y — until the polar-to-Cartesian conversion (x = cx + r·cos(a), y = cy + r·sin(a)) is automatic; the two drills here target exactly that recurrent conversion, framed inside real drawing tasks rather than isolated math. From there each new form is one twist on the same loop: grow the radius per step for an Archimedean spiral, cross two sinusoids at integer ratios for Lissajous figures, rotate a noise-length chord for the wave clock, and swap sin() for tan() in one family so the radius shoots off-scale near the asymptote and wraps back — a deliberately wilder sibling among the bounded forms. Just-in-time pointers for the composition phase: “Adding Perlin noise to a spiral’s radius” for organic wobble, “A custom pow(sin(x),n) shaping function” for deterministic character, and the beginShape/bezierVertex atoms for turning point streams into smooth paths.
Every required atom is load-bearing for the capstone: the trig and polar atoms generate the forms, the shaping/noise atoms give each family its variance — including the tan wave, whose unbounded spike-and-wrap behaviour drives the capstone’s wildest form family — and the vertex/bezier atoms build the paths. Supporting atoms — 3D sphere coordinates, organic geometry, iterative variance, and vector-vs-raster export for when a piece heads to a plotter or print — extend the same ideas into depth and craft without gating the piece.
Walkthrough
Trigonometry is a drawing tool: sin and cos turn an angle into a point, and everything from a circle to a mandala falls out of that one move. Paste each sketch into the p5 web editor and press Run. Each step is a complete, standalone sketch.
1 — sin is a wave. Walk x across the canvas and map sin(x) to y. That oscillation — the raw material of every curve here — becomes a smooth line ([[sine-wave-visual-curve]]).
function setup() { createCanvas(400, 400); }
function draw() {
background(15);
stroke(120, 200, 255);
noFill();
beginShape();
for (let x = 0; x < width; x += 4) {
let y = height / 2 + sin(x * 0.03) * 120;
vertex(x, y);
}
endShape();
}
2 — the unit circle: cos for x, sin for y. Sweeping an angle and plotting (cos a, sin a) draws a circle. This polar-to-Cartesian move — x = cx + r·cos(a), y = cy + r·sin(a) — is the one you’ll use forever ([[processing-trigonometry-sin-cos]], [[polar-coordinates-circular-drawing]]).
function setup() { createCanvas(400, 400); }
function draw() {
background(15);
stroke(255, 180, 80);
noFill();
let cx = 200, cy = 200, r = 150;
beginShape();
for (let a = 0; a < TWO_PI; a += 0.05) {
vertex(cx + cos(a) * r, cy + sin(a) * r);
}
endShape(CLOSE);
}
3 — grow the radius: a spiral. Let the radius increase with the angle and the circle unwinds into an Archimedean spiral — one number changed, a whole new form ([[spiral-as-growing-radius]]).
function setup() { createCanvas(400, 400); }
function draw() {
background(15);
stroke(200, 120, 255);
noFill();
let cx = 200, cy = 200;
beginShape();
for (let a = 0; a < TWO_PI * 8; a += 0.05) {
let r = a * 4;
vertex(cx + cos(a) * r, cy + sin(a) * r);
}
endShape();
}
4 — cross two sinusoids: a Lissajous curve. Drive x by sin(a·fx) and y by sin(a·fy) at an integer frequency ratio and you get a woven knot — the oscilloscope classic ([[lissajous-curve]] if present, else [[processing-trigonometry-sin-cos]]).
function setup() { createCanvas(400, 400); }
function draw() {
background(15);
stroke(120, 255, 180);
noFill();
let cx = 200, cy = 200, A = 150;
beginShape();
for (let a = 0; a < TWO_PI; a += 0.01) {
vertex(cx + sin(a * 3) * A, cy + sin(a * 2) * A);
}
endShape(CLOSE);
}
5 — a wave clock (animated mandala). Place spokes around a circle and let each spoke’s length ride a sin of its angle plus time — a breathing radial form. frameCount makes it move ([[rotational-drawing]]).
function setup() { createCanvas(400, 400); }
function draw() {
background(15, 40);
stroke(255, 200, 120, 180);
let cx = 200, cy = 200;
for (let a = 0; a < TWO_PI; a += TWO_PI / 90) {
let r = 120 + sin(a * 6 + frameCount * 0.03) * 50;
line(cx, cy, cx + cos(a) * r, cy + sin(a) * r);
}
}
6 — a family of forms, one driven by tan() (the capstone). Combine a spiral and a wave-clock, and add a third family whose radius uses tan(): near its asymptote the radius shoots off-scale and wraps back from the other side — a deliberately wild sibling among the bounded curves ([[tan-wave-asymptote]] if present):
function setup() { createCanvas(400, 400); }
function draw() {
background(12, 50);
let cx = 200, cy = 200;
// spiral
stroke(120, 200, 255, 150); noFill();
beginShape();
for (let a = 0; a < TWO_PI * 6; a += 0.05) { let r = a * 3; vertex(cx + cos(a) * r, cy + sin(a) * r); }
endShape();
// tan-driven spikes
stroke(255, 100, 140, 160);
for (let a = 0; a < TWO_PI; a += TWO_PI / 120) {
let r = constrain(tan(a * 2 + frameCount * 0.01) * 30, -180, 180);
line(cx, cy, cx + cos(a) * r, cy + sin(a) * r);
}
}
What good looks like. Crisp, intentional line work where the math is legible — a spiral that winds evenly, a Lissajous that closes cleanly, a tan family that spikes and wraps as a deliberate accent, not noise. If it looks messy, your angle step is too coarse (smaller increment = smoother curve) or forms overlap without contrast — separate them by colour or scale. The tan form should read as one wild gesture against the bounded ones, which is the whole point of pairing them. (Skill map: live-visualist Domain A4/B — generative form and composition.)
Now make it yours. Change the Lissajous ratio from 3:2 to 5:4. Add Perlin noise() to the spiral’s radius for an organic wobble. Colour each form by its angle with map(a, 0, TWO_PI, 0, 255). Increase the spiral’s turns. Replace line() spokes with vertex() for a continuous tan curve.
Runnable examples
Generated from the context/ instrument corpus by concept (redistributable idioms only). Do not edit — regenerate with gen-module-examples.mjs.
modulation-warp
osc(4).modulate(src(o0), 0.6).out(o0)
hydra-0022 · CC0-1.0
s0.initP5(); src(s0).modulate(noize(), 0.3).out()
p5live-0038 · CC0-1.0
feedback-trail
osc(4).modulate(src(o0), 0.6).out(o0)
hydra-0022 · CC0-1.0
function draw(){ fill(0, 20); rect(0, 0, width, height); circle(mouseX, mouseY, 40) }
p5live-0003 · CC0-1.0
radial-symmetry
osc(10).kaleid(5).out()
hydra-0010 · CC0-1.0
// sandbox
osc(10, 0.05, 1.3).kaleid(8).out()
// sandbox
p5live-0037 · CC0-1.0
oscillation
let y = height/2 + sin(frameCount * 0.05) * 100
p5live-0004 · CC0-1.0
float rings = abs(sin(length(uv)*20.0 - u_time*2.0));
glsl-0039 · public-domain
polar-warp
float r = length(uv); float a = atan(uv.y, uv.x);
glsl-0008 · public-domain
[rtx [fr, ft + 0.2*time], rty [fr, ft], 0.5] >> rgb
punctual-0032 · CC0-1.0
vector-drawing
beginShape(); for(let p of pts) curveVertex(p.x, p.y); endShape()
p5live-0016 · CC0-1.0
Atoms in this module
Required — these gate the capstone
Supporting — enrichment, not gating
Part of curricula
- Live Visualist — zero to performing live-coded & generative visuals — Generative canvas — colour, motion, and Hydra live-coding required
Unlocks — modules that require this one