Cinematica inversa e diretta

Braccio Meccanico

Un braccio meccanico è costituito da “links” (membri) e da giunti (servocomandi). Esiste una relazione analitica tra posizione angolare dei giunti e la “postura” del bracio.

Tramite semplici considerazioni geometriche è piuttosto semplice trovare la posizione (nello spazio) di un qualsiasi punto del braccio note le posizioni angolari dei singoli attuatori e le dimensioni dei singoli membri.

Meno semplice è la determinazione della posizione angolare dei giunti assegnato un obiettivo (es. posizione). Si tratta, in questo caso, della “cinematica inversa”.

LEGGE DEL COSENO

Uno dei metodi usati è quello basato sulla “Teorema del Coseno“, che mette in correlazione analitica la lunghezza dei membri e l’angolo tra due di essi:

cos(\gamma) = \frac{a^2 + b^2 - c^2}{2ab}

Dall grafico precedente, partendo dalle coordinate x e y (obiettivo) si risale alle posizioni angolari come segue (codice in processing):

void calcolo_angoli(){
  //TRIGONOMETRIA 
  I = sqrt(pow(X,2)+pow(Y,2)); //lunghezza ipotenusa
  theta_t = atan(Y/X); //angolo tra ipotenusa e asse X
  //LEGGE DEL COSENO
  angolo[1] = theta_t +acos((pow(L1,2)+pow(I,2)-pow(L2,2))/(2*L1*I));
  // NOTA: la posizione angolare viene riferita all'asse X
  angolo[2] = acos((pow(L1,2)-pow(I,2)+pow(L2,2))/(2*L1*L2));
}

Appare evidente che non sempre esistono soluzioni: se ad esempio le coordinate fossero tali da avere “ipotenusa” più lunga della massima estenzione del braccio, non si avrebbero soluzioni (fisicamente non può arrivarci!).

Lo spazio W dei punti p(x,y) ammissibili (raggiungibili) è il seguente:

W = \{p \in R^2: \parallel L1 - L2 \parallel <\parallel p \parallel < \parallel L1 + L2 \parallel\}

Notare, inoltre, che la solusione può non essere univoca.

A seguire un’animazione in Processing (vedasi come fare animazioni in Processing):

float theta_t;
float[] angolo = new float[2];

float L1 = 120,L2 = 120;

float X,Y;
float X0,Y0=0;
float I;

float t = 0,passo = 0.1;
float R = 40,r = 20;

void setup(){
  size(250,270,P3D);
  X0 = 1.2*(L1+L2)/2;
}

void draw(){
  background(155);
  translate(width/10,height/2);
  //equazione parametrica circonferenza
  X =X0+ R*cos(t);
  Y =Y0+ R*sin(t);
  t = t + passo; //incremento tempo 
  noFill();
  strokeWeight(2);
  ellipse(X0,Y0,2*R,2*R); 
  //ellipse(X0,Y0,2*R-r,2*R-r);
  //ellipse(X0,Y0,2*R+r,2*R+r);
  calcolo_angoli();//funzione calcolo angoli (legge del coseno)
  fill(0,255,0);
  strokeWeight(4);
  //costruzione "postura"
  rotateZ(-angolo[0]);
  line(0,0,L1,0);
  ellipse(0,0,r,r);
  translate(L1,0);
  rotateZ(PI-angolo[1]);
  line(0,0,L2,0);
  ellipse(0,0,r,r);
  fill(255,0,0);

  ellipse(L2,0,r,r);
}

void calcolo_angoli(){
  //TRIGONOMETRIA
  I = sqrt(pow(X,2)+pow(Y,2));
  theta_t = atan(Y/X);
  //LEGGE DEL COSENO
  angolo[0] = theta_t +acos((pow(L1,2)+pow(I,2)-pow(L2,2))/(2*L1*I));
  angolo[1] = acos((pow(L1,2)-pow(I,2)+pow(L2,2))/(2*L1*L2));
}

Le cose si complicano se il numero di membri è maggiore di due.

FABRIK

FABRIK è l’acronimo (Forward And Backward Reaching Inverse Kinematics”).

In figura viene mostrato il principio che sta alla base del FABRIK, per il cui utilizzo è necessario conoscere lo stato iniziale:

Si vuole spostare il punto V1 nel punto t (target): il tratto v1-v2 viene spostato in modo tale da cadere sulla direzione della congiungente tra t e v2. Ovviemente per garantire la continuità meccanica è necessario che anche il secondo tratto venga traslato e ruotato in modo da farlo giacere sulla direzione della congiungente v2-v3. Il procedimento si ripete per tutti i membri.

A seguire un esempio in Processing:

float L = 10; //lunghezze
int n = 50; //numero elementi del braccio 
PVector a,b,dir;//vettori di servizio 

float Xt,Yt;//coordinate "target" t

//MATRICE DELLE COORDINATE DELLE ARTICOLAZIONI 
float[][] coordinate = new float[2][n+1]; 

float t=0;
void setup(){
  size(500,500);
  
  //NECESSARIO IMPOSTARE UNO STATO INIZIALE:
  for (int i = 0; i <n; i = i+1) {
      coordinate[1][i] = 0; 
      coordinate[0][i] = (i+1)*L;
  }
  
}

void draw(){
  background(155);
  Xt = width/2 +width/3*cos(t);
  Yt = height/2 +width/3*sin(t);
  if(t>2*PI){
    t = 0;//periodico 2*PI
  }
  t = t+0.1;
  //Xt = mouseX;
  //Yt = mouseY;
  delay(100);
  coordinate[0][0] = Xt;
  coordinate[1][0] = Yt;
  
  for (int i = 0; i <n; i = i+1) {
  //per ogni segmento se ne prendono le coordinate degli estremi 
  a = new PVector(coordinate[0][i],coordinate[1][i]);
  b = new PVector(coordinate[0][i+1],coordinate[1][i+1]);
  dir = PVector.sub(a,b);//vettore nella direzione a-b
  dir.setMag(L);//settaggio lunghezza del suddetto vettore
  dir.mult(-1);//rotazione
  b = PVector.add(a,dir);//aggiunta ad "a"
  
  //aggiornamento matrice delle coordinate:
  coordinate[0][i+1] = b.x;
  coordinate[1][i+1] = b.y;
  }
  
  //tracciamento 
  for (int i = 0; i <n; i = i+1) {
  strokeWeight(5);
  line(coordinate[0][i] ,coordinate[1][i],coordinate[0][i+1] ,coordinate[1][i+1]);
  }
  
}

è possibile cambiare sia la lunghezza L dei singoli elementi che il numero totale n.

simulazione con 6 elementi
simulazione con 50 elementi

Matteo Gentileschi

Lascia un commento