Pendule Double

Dans ce post, nous allons nous intéresser à simuler et visualiser un pendule double.

Le pendule double consiste en un pendule à l’extrémité duquel on accroche un autre pendule. C’est un exercice classique de mécanique. On a donc deux tiges de longueur l1 et l2, de masse nulle ainsi que de deux masses m1 et m2. Son évolution est généralement chaotique.

Résultat de la simulation


I°) Les equations de mouvement

Pendule double, equations de mouvement

Ces equations proviennent des equations cinématiques, et des forces du pendule double. Pour plus d’informations sur la manière de les obtenir, allez sur ce site :
https://www.myphysicslab.com/pendulum/double-pendulum-en.html.


II°) Résultats


III°) Code

Ce code est composé en deux parties :

  1. La simulation et visualisation du pendule double,
  2. La création d’une interface utilisateur interactive.


III-1°) Simulation et visualisation

Le double pendule nécessite un bon paquet d’informations, notamment la longueur des axes : r1 et r2, la masses des pendules : m1 et m2, l’angle des pendules : a1 et a2, leurs vitesses a1_v et a2_v ainsi que leurs acceleration a1_a et 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

D’autres paramètres liés à l’affichage du pendule double, et particulièrement à sa trace sont nécessaires :

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

// Translation parameters
float cx;
float cy;

Enfin, des paramètres pour convertir les coordonnées polaires en cartésiennes sont nécessaires.

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

Dans la partie setup(), nous retrouvons : l’initialisation des couleurs que l’on souhaite utiliser, de la trace du pendule double et enfin le calcul des coordonnées cartésiennes des deux pendules.

// *****************************************************************************
//                                     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);
}

Enfin, dans la partie draw(), nous avons :

// *****************************************************************************
//                                     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°) Interface utilisateur (GUI)

Dans cette partie, j’utilise des classes pour créer mon bouton Play/Pause, ainsi que les HScrollbar. Je les instancie dans la partie maître, et check en permanent leur état afin de rendre cette interface interactive.

La classe Button pour le bouton Play/Pause est la suivante :

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);
  }
}

La classe Hscrollbar pour les sliders est la suivante :

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;
  }
}

Enfin, la classe maître devient :

 // *****************************************************************************
//                                   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;
  }
}

Laisser un commentaire

Concevoir un site comme celui-ci avec WordPress.com
Commencer
search previous next tag category expand menu location phone mail time cart zoom edit close