Creative Coding Blog

Maths, coding and art

Lissajous figures with Processing

A Lissajous figure, sometimes called a Lissajous curve, is formed by these parametric equations:

x = r * cos(A*t)
y = r * sin(B*t)

Essentially, you can think of it a the point (x, y) oscillates from left to right A times per second, and also oscillates up and down B times a second. By changing A and B we can create some interesting curves.

Normally, you should choose A and B to be small integers. This creates a closed curve as t varies between 0 and 2*pi

Examples

If we set A to 1 and B to 2, the y value oscillates twice as quickly as the x value, so it creates a figure of eight shape:

If we set A to 3 and B to 4 we get a more complex shape:

The code

Here is the code to create a Lissajous figure in Processing:

void lissajous(float r, float cx, float cy, float A, float B)
{
    PShape s = createShape();
    s.beginShape();
    for (float t = 0; t < TWO_PI; t += 0.01)
    {
        float x = cx + r * cos(A*t);
        float y = cy + r * sin(B*t);
        s.vertex(x, y);
    }
    s.endShape(CLOSE);
    
    shape(s);
}

void setup()
{
    size(600, 600);
}

void draw()
{
    clear();
    background(64);
    noFill();
    strokeWeight(2);
    stroke(255, 0, 0);
    lissajous(250, 300, 300, 3, 4);
}

The lissajous function takes these parameters:

  • r - the radius (ie size) of the shape.
  • cx, cy - the centre of the shape in the x and y directions.
  • A, B - the parameters in the equation.

The code inside this function is fairly similar to the circle example. In the loop we calculate the points (x, y) for values of t from 0 to 2*pi. We add the points as the vertices of a PShape. The final shape is an approximation to the Lissajous curve. and we draw it by calling the shape function.

In the draw function we create the shape with a radius of 250 and a centre (300, 300), which fits our image size of 600 pixels square.

Try different values of A and B. You should use integer values to get a closed shape.

Exploring different values

In this image we have included every combination of A and B from 1 to 6, to create a map of Lissajous figures:

In this map, the column gives the value of A (the first column is A = 1, the second column is A = 2 etc), and the row gives the value of B.

You will notice that all the shapes on the leading diagonal are circles. When A and B are both 1, the equations are:

x = r * cos(t)
y = r * sin(t)

which you might recognise as the parametric equation of a circle. When A and B are both 2, the equations are:

x = r * cos(2*t)
y = r * sin(2*t)

This creates exactly the same shape, the point just rotates around the circle twice as fast. As t moves from 0 to 2*pi, the point goes round twice rather than just once. When A and B are both 3, it also draws a circle but the point goes round 3 times, etc.

You will also notice that the shape A = 1, B = 2 is the same shape as A = 2, B = 4 and also A = 3, B = 6, for the same reason.

One final interesting thing is the curve A = 2, B = 1 (second column, first row). This curve is actually a parabola!

Code for the Lissajous map

The code is the same as before, we have just used a different draw function:

void draw()
{
    clear();
    background(64);
    noFill();
    strokeWeight(1);
    stroke(255, 0, 0);
    for (int i = 0; i < 6; i++)
    {
      for (int j = 0; j < 6; j++)
      {
        lissajous(40, 50+100*i, 50+100*j, i+1, j+1);
      }
    }
}

Here we use a nested loop to step through every combination of A (given by i+1), and B (given by j+1). The images are much smaller (r is 40) and each image is placed in a different position on the page.

Adding a phase term

One last thing we can do with Lissajous curves is to add a phase term to the x equation:

x = r * cos(A*t + P)

This means that rather than starting at zero, the x term effectively starts at P. We can change the lissajous function to accept the extra parameter:

void lissajous(float r, float cx, float cy, float A, float B, float P)
{
    PShape s = createShape();
    s.beginShape();
    for (float t = 0; t < TWO_PI; t += 0.01)
    {
        float x = cx + r * cos(A*t + P);
        float y = cy + r * sin(B*t);
        s.vertex(x, y);
    }
    s.endShape(CLOSE);
    
    shape(s);
}

Let’s draw a set of curves with different values of P. The first curve has P = 0, with P increasing by 0.2 in each successive image (in reading order):

As you can see, the phase term makes slight changes to the shape. We can use this to create interesting variations.

Here is the complete code for the image above:

void lissajous(float r, float cx, float cy, float A, float B, float P)
{
    PShape s = createShape();
    s.beginShape();
    for (float t = 0; t < TWO_PI; t += 0.01)
    {
        float x = cx + r * cos(A*t + P);
        float y = cy + r * sin(B*t);
        s.vertex(x, y);
    }
    s.endShape(CLOSE);
    
    shape(s);
}

void setup()
{
    size(600, 600);
}

void draw()
{
    clear();
    background(64);
    noFill();
    strokeWeight(2);
    
    stroke(255, 0, 0);
    lissajous(65, 75, 75, 3, 2, 0);
    lissajous(65, 225, 75, 3, 2, 0.2);
    lissajous(65, 375, 75, 3, 2, 0.4);
    lissajous(65, 525, 75, 3, 2, 0.6);
    lissajous(65, 75, 225, 3, 2, 0.8);
    lissajous(65, 225, 225, 3, 2, 1);
    lissajous(65, 375, 225, 3, 2, 1.2);
    lissajous(65, 525, 225, 3, 2, 1.4);
}