• Home
  • Blog
    • Programming
    • Art
    • Recipes
    • Home Automation
    • Life
    • Friday Faves
    • Books
    • Writing
    • Games
    • Web Accessibility
    • Advent of Code
  • Projects
  • GLSL Shader Art
  • Glitch Art

  • Github
  • Bluesky
  • Mastodon
  • Duolingo
  • LinkedIn
  • RSS
  • Bridgy Fed

mary.codes

Web development, art, and tinkering with fun projects. JavaScript, Python, WebGL, GLSL, Rust, Lua, and whatever else I find interesting at the moment.

Pendulums and sine waves in P5.js

Mary KnizeBlog iconJan 31st, 2024
4 min read

Art

Creating algorithmic art with pendulum simulations and sine waves using P5.js.

I recently created some very cool art using simulated pendulums and sine waves!

Pendulums

A simple pendulum

My initial pendulum simulation was helped along by The Coding Train's video of a simple pendulum simulation.

Of course, if you're like me and understand better by reading, you can also read the pendulum section on The Nature of Code, which is also written by Daniel Shiffman from The Coding Train.

let pendulum;
let bg = '#000000';

function setup() {
  createCanvas(window.innerWidth, window.innerHeight);
  background(bg);
  let len = height / 2;
  pendulum = new Pendulum(len);
}

function draw() {
  background(bg);
  pendulum.update();
}

class Pendulum {
  constructor(len) {
    this.len = len;
    this.angle = PI / 4;
    this.angleV = 0; // angular velocity
    this.angleA = 0; // angular acceleration
    this.bob = createVector();
    this.origin = createVector(width / 2, 0);
    this.gravity = 0.1;
  }

  update() {
    // Calculate force, acceleration, velocity, angle
    const force = this.gravity * sin(this.angle);
    this.angleA = (-1 * force) / this.len;
    this.angleV += this.angleA;
    this.angle += this.angleV;

    // Calculate position of bob each frame
    this.bob.x = this.len * sin(this.angle) + this.origin.x
    this.bob.y = this.len * cos(this.angle) + this.origin.y;

    // Draw string
    stroke(color(255, 255, 255, 100));
    strokeWeight(1);
    line(this.origin.x, this.origin.y, this.bob.x, this.bob.y);

    // Draw pendulum bob
    stroke(color(255, 255, 255, 255));
    strokeWeight(20);
    noFill();
    point(this.bob.x, this.bob.y);
  }
}

From looking at the code, you can see that I've created a class that constructs a new pendulum on setup, then draws the update to the pendulum every frame.

Multiple pendulums

Adding multiple pendulums requires creating a loop that creates pendulums with different string lengths and pushes them to an array. On each call of the draw function, it loops through the pendulums and updates each of them.

let max_pendulums = 20;
let pendulums = [];
let bg = '#000000';

function setup() {
  createCanvas(1920, 1080);
  background(bg);

  let len = 100;
  for (let i = 0; i < max_pendulums; i++) {
    pendulums.push(new Pendulum(len));
    len += (height - 100) / max_pendulums;
  }
}

function draw() {
  background(bg);
  for (let pendulum of pendulums) {
    pendulum.update();
  }
}

class Pendulum {
  ...
}

Bouncing pendulums

Next, I want to make these pendulums bouncy. This is done by keeping track of a global time variable, and getting the sine of that time. Then we multiply the sign by the height of the wave and add that to the base length of the pendulum string.

The general formula for this is: pendulum.length + sin(time) * height of bounce

After calculating the amount of bounce, that value is used in the this.bob.x and this.bob.y calculations. This makes the pendulums appear bouncy as they sway.

let time = 0;

function setup() {
    ...
}

function draw() {
    time += 0.1;
    ...
}

class Pendulum {
    constructor(len) {
        ...
    }

    update() {
        // Lengthen and shorten the pendulum's string with a sine wave
        let len = this.len + sin(time) * (height / (max_pendulums * 2.5));

        // Calculate force, acceleration, velocity, angle
        ...

        // Calculate position of bob each frame
        this.bob.x = len * sin(this.angle) + this.origin.x
        this.bob.y = len * cos(this.angle) + this.origin.y;

        // Draw string
        ...

        // Draw pendulum bob
        ...
    }
}

Drawing waves

Now, to draw the rest of the owl. To create the cool rainbow effects (I think they look like skeins of embroidery floss), we need to remove the string and the "bob" from the pendulum, and draw a line from the last position of the pendulum to the current position.

I'm also going to use the lerpColor() function from P5.js to smoothly transition between colors. I'm doing this in the HSL color space because it creates a more attractive, vibrant gradient than RGB. The color is added to the pendulum class as a property, and is passed in as an argument when the pendulum is created.

You'll also note that I've removed the background method from the draw function. We want the lines to persist, not be wiped away with every call to draw().

Once you have the basic mechanics in place, you can let your creativity go wild!

function setup() {
  createCanvas(3840, 2160);
  colorMode(HSL, 255);
  background(bg);

  let col1 = '#ffbe6f';
  let col2 = '#b894ff';
  let len = 10;
  for (let i = 0; i < max_pendulums; i++) {
    let col = lerpColor(color(col1), color(col2), i / max_pendulums);
    pendulums.push(new Pendulum(len, col));
    len += (height - 210) / max_pendulums;
    stopframe += 100 * (i + 1);
  }
}

function draw() {
  time += 0.3;
  for (let pendulum of pendulums) {
    pendulum.update();
  }
}

class Pendulum {
  constructor(len, col) {
    this.angle = PI / 2; // PI / 2 makes a semicircle!
    ...
    this.last = createVector();
    this.col = col;
  }

  update() {
    // Lengthen and shorten the pendulum's string with a sine wave
    let len = this.len + sin(time) * (height / (max_pendulums * 2.5));
    
    // Calculate force, acceleration, velocity, angle
    ...

    // Calculate position of bob each frame
    ...

    // If this is the first frame, set the last position 
    // of the pendulum bob to the current position
    if (this.last.x === 0) {
      this.last.x = this.bob.x;
      this.last.y = this.bob.y;
    }

    stroke(this.col);
    strokeWeight(1);
    noFill();
    line(this.last.x, this.last.y, this.bob.x, this.bob.y);
    
    // Update the last position to the current position
    this.last.x = this.bob.x;
    this.last.y = this.bob.y;
  }
}

Variations

By changing some of the variables, it's possible to get a wide variety of different effects. Reducing the amount that the time increases widens the sine wave. Multiplying the force by 1 instead of -1 will make the pendulum defy gravity.

Dotted

Around

Alternate

Rings

You can check out the code for these variations on my GitHub.


Other Art posts

I started painting again

Kitbashing and 3D printing George Washingtits (NSFW)

Shape packing with P5.js

3D printing generative art with P5.js and Blender

Creating an Animated GIF on the Command Line

Latest posts

I started painting again

100 days of Japanese on Duolingo

How I'm organizing my work in 2024

Watch the documentary 'Good Night Oppy' if you're feeling science-y

Sculpted robo-goldfish, a rainbow office building, and a look inside click farms