Double Pendulum

In this post, we will simulate and visualize a double pendulum.

In physics and mathematics, in the area of dynamical systems, a double pendulum is a pendulum with another pendulum attached to its end, and is a simple physical system that exhibits rich dynamic behavior with a strong sensitivity to initial conditions. The motion of a double pendulum is governed by a set of coupled ordinary differential equations and is chaotic.
Cf Wikipedia.

This image has an empty alt attribute; its file name is image-13.png
Result of the simulation


I°) Equations of Motion

This image has an empty alt attribute; its file name is image-12.png
Double Pendulum, equations of motion

Theses equations come from the kinematic equations and the forces from the double pendulum. For more explanation about the way to get them, go on this website :
https://www.myphysicslab.com/pendulum/double-pendulum-en.html


II°) Results


III°) Code

This code is composed by two parts :

  1. The simulation and visualization of the double pendulum,
  2. The creation of an interactive user interface.


III-1°) Simulation and visualization

The double pendulum necesite a bunch of informations, including the size of the axis : r1 and r2, the mass of the pendulums : m1 and m2, the angle of them : a1 and a2, their speed : a1_v and a2_v, and finally their acceleration a1_a and a2_a.

// *****************************************************************************
//                                   PARAMETERS
// *****************************************************************************
// Double pendulum parameters
float r1 = 150;  // Radius axe n°1
float r2 = 130;  // Radius axe n°2
float m1 = 50;   // Masse object n°1
float m2 = 20;   // Masse object n°2
float a1 = PI/2; // Angle n°1
float a2 = PI/2; // Angle n°2
float a1_v = 0;  // Velocity n°1
float a2_v = 0;  // Velocity n°2
float a1_a = 0;  // Acceleration n°1
float a2_a = 0;  // Acceleration n°2
float g = 0.1;   // gravitational acceleration

Some others parameters are linked to the display of the double pendulum, and mainly to his ghost :

// Ghost parameters
float px2 = -1;
float py2 = -1;
PGraphics canvas;
int cnt = 0;
color[] colours = new color[4];

// Translation parameters
float cx;
float cy;

Finally, we need some parameters for converting polar coordinates into cartesian ones :

// Cartesian position
float x1, x2, y1, y2;

In the setup() part, we find the init of the colors that we wish to use, the ghost and first calculation of the cartesians coordinates.

// *****************************************************************************
//                                     SETUP
// *****************************************************************************
void setup() {
  fullScreen();
  //size(900, 900);
  
  // Init colors 
  colorMode(RGB, 255, 255, 255, 1);
  pixelDensity(1);
  colours[0] = color(229, 252, 255, 0.3);
  colours[1] = color(122, 79, 242, 0.7);
  colours[2] = color(230, 9, 116, 0.7);
  colours[3] = color(229, 252, 255);

  // Init ghost
  cx = width/2;
  cy = height/3;
  canvas = createGraphics(width, height);
  canvas.beginDraw();
  canvas.background(colours[3]);
  canvas.endDraw();

  // Let's do the calculation at least once
  x1 = r1 * sin(a1);
  y1 = r1 * cos(a1);
  x2 = x1 + r2 * sin(a2);
  y2 = y1 + r2 * cos(a2);
}

In the draw() part, we need :

// *****************************************************************************
//                                     DRAW
// *****************************************************************************
void draw() {
  image(canvas, 0, 0);
  stroke(170);
  fill(240);
  rect(width/5, 0.72*height, 0.57*width, 0.24*height);
  noStroke();
  noFill();

  // Display pendulum
  stroke(0);
  strokeWeight(2);
  translate(cx, cy);
  line(0, 0, x1, y1);
  fill(0);
  ellipse(x1, y1, m1, m1);
  line(x1, y1, x2, y2);
  fill(0);
  ellipse(x2, y2, m2, m2);

  // Motion equations
  float num1 = -g*(2*m1 + m2) * sin(a1) - m2*g*sin(a1-2*a2) -2*sin(a1 - a2)*m2*(a2_v*a2_v*r2 + a1_v*a1_v*r1*cos(a1-a2));
  float den1 = r1*(2 * m1 + m2 - m2 * cos(2*a1 - 2*a2));
  a1_a = num1/den1;
  float num2 = 2*sin(a1-a2)*(a1_v*a1_v*r1*(m1+m2) + g*(m1+m2)*cos(a1) + a2_v*a2_v*r2*m2*cos(a1-a2));
  float den2 = r2*(2*m1 + m2 - m2*cos(2*a1 - 2*a2));
  a2_a = num2/den2;

  // Calculate cartesian coordinates
  x1 = r1 * sin(a1);
  y1 = r1 * cos(a1);
  x2 = x1 + r2 * sin(a2);
  y2 = y1 + r2 * cos(a2);

  // Get velocity and acceleration
  a1_v += a1_a;
  a2_v += a2_a;
  a1 += a1_v;
  a2 += a2_v;

  // Let's add a damping factor
  a1_v *= 0.999; 
  a2_v *= 0.999; 

  // Displaying the ghost of the double pendulum
  canvas.beginDraw();
  canvas.translate(cx, cy);
  canvas.strokeWeight(3);
  if (frameCount > 1) {
    // Let's put a color gradient effect between purple and red colors. 
    float trans = map(x2, -1*(r1+r2), r1 + r2, 0, 1);
    color lineColour = lerpColor(colours[1], colours[2], trans);
    canvas.stroke(lineColour);
    canvas.line(px2, py2, x2, y2);
  }
  canvas.endDraw();

  px2 = x2;
  py2 = y2;
}


III-2°) General User Interface (GUI)

In this part, i’m using some class for creating the Play/Pause button and the HScrollbar. I instanciate them in the main part, and check each frame their states in order to make an interactive interface.

The class Button is the following one :

class Button {
  // *****************************************************************************
  //                                   PARAMETERS
  // *****************************************************************************
  float xpos; // X pos of the button
  float ypos;
  float widthButton; 
  float heightButton;
  color rectColor;
  color rectHighlight;
  boolean rectOver = false;
  boolean locked = false;
  int cnt = 0;
  String name;
  boolean state = false;



  // *****************************************************************************
  //                                   CONSTRUCTOR
  // *****************************************************************************
  Button(float xpos_, float ypos_, float widthButton_, float heightButton_, String name_) {
    xpos = xpos_;
    ypos = ypos_;
    widthButton = widthButton_;
    heightButton = heightButton_;
    name = name_;
    rectColor = color(210);
    rectHighlight = color(190);
  }



  // *****************************************************************************
  //                                   METHODS
  // *****************************************************************************

  // ***********************
  boolean overRect(float x, float y, float width, float height) {
    if (mouseX >= x && mouseX <= x+width && 
      mouseY >= y && mouseY <= y+height) {
      return true;
    } else {
      return false;
    }
  }


  // ***********************
  boolean update() {
    if (overRect(xpos, ypos, widthButton, heightButton)) {
      rectOver = true;
      if (mousePressed == true && locked == false) {
        locked = true;
        state = !state;
        delay(200);
        locked = false;
      }
    } else {
      rectOver = false;
    }
    return state;
  }


  // ***********************
  void display() {  
    noStroke();
    if (rectOver) {
      fill(rectHighlight);
    } else {
      fill(rectColor);
    }
    rect(xpos, ypos, widthButton, heightButton, 10, 10, 10, 10);
    textSize(16);
    textAlign(CENTER, CENTER);
    fill(0);
    text(name, xpos, ypos-4, widthButton, heightButton);
    textAlign(CENTER);
  }
}

The class HScrollbar for the sliders is the following one :

class HScrollbar {
  // *****************************************************************************
  //                                   PARAMETERS
  // *****************************************************************************
  int swidth, sheight;    // width and height of bar
  float xpos, ypos;       // x and y position of bar
  float spos, newspos;    // x position of slider
  float sposMin, sposMax; // max and min values of slider
  int loose;              // how loose/heavy
  boolean over;           // is the mouse over the slider?
  boolean locked;
  float ratio;
  String name;
  int sh, sw;
  float min, max;
  float spos_map;



  // *****************************************************************************
  //                                   CONSTRUCTOR
  // *****************************************************************************
  HScrollbar (float xp, float yp, int sw_, int sh_, int l, String name_, float min_, float max_, float beginValue_) {
    swidth = sw_;
    sheight = sh_;
    int widthtoheight = sw_ - sh_;
    ratio = (float)sw_ / (float)widthtoheight;
    xpos = xp;
    ypos = yp-sheight/2;
    spos = xpos + swidth/2 - sheight/2;
    newspos = spos;
    sposMin = xpos;
    sposMax = xpos + swidth - sheight;
    loose = l;
    name = name_;
    min = min_;
    max = max_;
    spos_map = beginValue_;
  }



  // *****************************************************************************
  //                                   METHODS
  // *****************************************************************************

  // ***********************
  void update() {
    if (overEvent()) {
      over = true;
    } else {
      over = false;
    }
    if (mousePressed && over) {
      locked = true;
    }
    if (!mousePressed) {
      locked = false;
    }
    if (locked) {

      newspos = constrain(mouseX-sheight/2, xpos, xpos + swidth - sheight);
    }
    if (abs(newspos - spos) > 1) {
      spos = spos + (newspos-spos)/loose;
      spos_map = map(spos, xpos, xpos + swidth - 1.5*sheight, min, max);
    }
  }


  // ***********************
  float constrain(float val, float minv, float maxv) {
    return min(max(val, minv), maxv);
  }


  // ***********************
  boolean overEvent() {
    if (mouseX > xpos && mouseX < xpos+swidth &&
      mouseY > ypos && mouseY < ypos+sheight) {
      return true;
    } else {
      return false;
    }
  }


  // ***********************
  void display() {
    noStroke();
    fill(204);
    rect(xpos, ypos, swidth, sheight);
    textSize(20);
    textAlign(CENTER);
    fill(0);
    text(name, xpos + swidth + 30, ypos + 0.8*sheight );
    text((int)spos_map, spos, ypos-2);
    if (over || locked) {
      fill(0, 0, 0);
    } else {
      fill(102, 102, 102);
    }
    rect(spos, ypos, sheight, sheight);
  }


  // ***********************
  float getPos() {
    return (float)spos_map;
    //return (float)spos_map * ratio;
  }
}

Finally, the main part is becoming :

// *****************************************************************************
//                                   PARAMETERS
// *****************************************************************************
// Double pendulum parameters
float r1 = 150;  // Radius axe n°1
float r2 = 130;  // Radius axe n°2
float m1 = 50;   // Masse object n°1
float m2 = 20;   // Masse object n°2
float a1 = PI/2; // Angle n°1
float a2 = PI/2; // Angle n°2
float a1_v = 0;  // Velocity n°1
float a2_v = 0;  // Velocity n°2
float a1_a = 0;  // Acceleration n°1
float a2_a = 0;  // Acceleration n°2
float g = 0.1;   // gravitational acceleration

// Translation parameters
float cx;
float cy;

// Ghost parameters
float px2 = -1;
float py2 = -1;
PGraphics canvas;
int cnt = 0;
color[] colours = new color[4];

// Cartesian position
float x1, x2, y1, y2;

// GUI Parameters
HScrollbar hs_r1, hs_r2, hs_m1, hs_m2, hs_a1, hs_a2, hs_g; 
Button b_start, b_stop, b_reset;



// *****************************************************************************
//                                     SETUP
// *****************************************************************************
void setup() {
  fullScreen();
  //size(900, 900);
  
  // Init colors 
  colorMode(RGB, 255, 255, 255, 1);
  pixelDensity(1);
  colours[0] = color(229, 252, 255, 0.3);
  colours[1] = color(122, 79, 242, 0.7);
  colours[2] = color(230, 9, 116, 0.7);
  colours[3] = color(229, 252, 255);

  // Init ghost
  cx = width/2;
  cy = height/3;
  canvas = createGraphics(width, height);
  canvas.beginDraw();
  canvas.background(colours[3]);
  canvas.endDraw();

  // Let's do the calculation at least once
  x1 = r1 * sin(a1);
  y1 = r1 * cos(a1);
  x2 = x1 + r2 * sin(a2);
  y2 = y1 + r2 * cos(a2);

  hs_r1 = new HScrollbar(width/4, 0.77*height, width/6, 16, 1, "r1", 10, 200, 150);
  hs_r2 = new HScrollbar(width/4, 0.82*height, width/6, 16, 1, "r2", 10, 200, 130);
  hs_m1 = new HScrollbar(width/4, 0.87*height, width/6, 16, 1, "m1", 10, 100, 50);
  hs_m2 = new HScrollbar(width/4, 0.92*height, width/6, 16, 1, "m2", 10, 100, 20);  
  hs_g  = new HScrollbar(width/4 + 80 + width/6, 0.87*height, width/6, 16, 1, "g", 0, 20, 1); 

  b_start = new Button(width/3 + 40 + width/6, 0.77*height, 130, 35, "Start/Pause");
}




// *****************************************************************************
//                                     DRAW
// *****************************************************************************
void draw() {
  image(canvas, 0, 0);
  stroke(170);
  fill(240);
  rect(width/5, 0.72*height, 0.57*width, 0.24*height);
  noStroke();
  noFill();

  // Get all GUI parameters
  hs_r1.update();
  hs_r1.display();
  hs_r2.update();
  hs_r2.display();
  hs_m1.update();
  hs_m1.display();  
  hs_m2.update();
  hs_m2.display();
  hs_g.update();
  hs_g.display();

  // And use them here...
  boolean start = b_start.update();
  b_start.display();
  r1 = hs_r1.getPos();
  r2 = hs_r2.getPos();
  m1 = hs_m1.getPos();
  m2 = hs_m2.getPos();
  g = hs_g.getPos();


  // Display pendulum
  stroke(0);
  strokeWeight(2);
  translate(cx, cy);
  line(0, 0, x1, y1);
  fill(0);
  ellipse(x1, y1, m1, m1);
  line(x1, y1, x2, y2);
  fill(0);
  ellipse(x2, y2, m2, m2);



  if (start) {
    // Motion equations
    float num1 = -g*(2*m1 + m2) * sin(a1) - m2*g*sin(a1-2*a2) -2*sin(a1 - a2)*m2*(a2_v*a2_v*r2 + a1_v*a1_v*r1*cos(a1-a2));
    float den1 = r1*(2 * m1 + m2 - m2 * cos(2*a1 - 2*a2));
    a1_a = num1/den1;
    float num2 = 2*sin(a1-a2)*(a1_v*a1_v*r1*(m1+m2) + g*(m1+m2)*cos(a1) + a2_v*a2_v*r2*m2*cos(a1-a2));
    float den2 = r2*(2*m1 + m2 - m2*cos(2*a1 - 2*a2));
    a2_a = num2/den2;

    // Calculate cartesian coordinates
    x1 = r1 * sin(a1);
    y1 = r1 * cos(a1);
    x2 = x1 + r2 * sin(a2);
    y2 = y1 + r2 * cos(a2);

    // Get velocity and acceleration
    a1_v += a1_a;
    a2_v += a2_a;
    a1 += a1_v;
    a2 += a2_v;

    // Let's add a damping factor
    a1_v *= 0.999; 
    a2_v *= 0.999; 

    // Displaying the ghost of the double pendulum
    canvas.beginDraw();
    canvas.translate(cx, cy);
    canvas.strokeWeight(3);
    if (frameCount > 1) {
      // Let's put a color gradient effect between purple and red colors. 
      float trans = map(x2, -1*(r1+r2), r1 + r2, 0, 1);
      color lineColour = lerpColor(colours[1], colours[2], trans);
      canvas.stroke(lineColour);
      canvas.line(px2, py2, x2, y2);
    }
    canvas.endDraw();

    px2 = x2;
    py2 = y2;
  }
}

Leave a comment

Design a site like this with WordPress.com
Get started
search previous next tag category expand menu location phone mail time cart zoom edit close