Capitolo 4 - la legge

Ora abbiamo quasi tutto quello che ci serve per simulare la legge di gravitazione universale:

Uno dei vantaggi della programmazione ad oggetti è la possibilità di riutilizzare classi scritte da altri o da noi stessi per programmi diversi. Le classi già scritte sono una sorta di cassetta degli attrezzi dalla quale possiamo attingere per prendere gli strumenti che ci servono per realizzare i nostri progetti. Processing ha una vastissima collezione di classi e di librerie (collezioni di classi) alcune delle quali sono già disponibili nel linguaggio di base.

Tra queste classi una delle più importanti è quella che si chiama PVector e permette di usare i vettori e l'algebra vettoriale in modo semplice e completo. Invece di trattare ogni volta separatamente le due (o tre) componenti delle grandezze vettoriali, possiamo fare i calcoli pensandole direttamente come vettori. Per seguire un tutorial completo sui vettori e sulla classe PVector in Processing, guarda nella pagina della sitografia.

Ecco come si modifica la classe Ball prevedendo l'uso dei vettori e della classe PVector

class Ball
{
  // proprietà
  //float x, y, vx, vy, ax, ay;
  PVector pos, v, a;
  float dim;
  color col;

  // costruttori
  Ball()
  {
    pos = new PVector(width/2, height/2);
    v = new PVector(random(-5, 5), random(-5, 5));
    a = new PVector(random(-0.2, 0.2), random(-0.2, 0.2));
    dim = (int)random(5, 30);
    col = color(random(255), random(255), random(255));
  }

  Ball(PVector _pos, PVector _v, PVector _a, int _dim)
  {
    pos = new PVector(_pos.x, _pos.y);
    v = new PVector(_v.x, _v.y);
    a = new PVector(_a.x, _a.y);
    dim = _dim;
    col = color(random(255), 0, 0);
  }

  // metodi
  void show()
  {
    pushMatrix();
    translate(pos.x, pos.y);
    stroke(0);
    strokeWeight(2);
    fill(col);
    //ellipse(0, 0, dim, dim);
    point(0,0);
    stroke(255, 0, 0);
    strokeWeight(2);
    line(0, 0, v.x*2, v.y*2);
    stroke(0, 255, 0);
    strokeWeight(2);
    line(0, 0, a.x*1000, a.y*1000);
    popMatrix();
  }

  void update(PVector g, float m)
  {
    // ricavo la direzione di a come differenza tra il vettore gravità e il vettore posizione dell'oggetto
    PVector forza = PVector.sub(g, pos);
    // normalizzo a 1 il vettore a
    forza.normalize();
    // ricavo la distanza tra oggetto e centro di gravità
    float d = g.dist(pos);
    if (d < 0.01) d = 0.01;
    // riscalo il modulo di a per il reciproco della distanza al quadrato
    forza.mult(m*dim/sq(d));
    a = forza.mult(1./dim);

    v.add(a);
    pos.add(v);
  }
}

Come ogni oggetto, un vettore deve essere dichiarato e poi creato (istanziato) con l'istruzione new prima di essere utilizzato.

Un'altra grande potenzialità del linguaggio di Processing è quello di avere tutte le istruzioni necessarie per gestire le trasformazioni affini. Se devo ad esempio disegnare un oggetto complesso (quindi costituito di più parti) in un punto del canvas, sarà sicuramente più difficile se il sistema di riferimento è fisso nel punto in alto a sinistra perché dovrò farmi tutti i calcoli delle coordinate riferite a quell'origine. Potendo invece spostare l'origine del sistema di riferimento nel punto che mi è più comodo, posso evitare di fare tutti i calcoli relativi. A questo servono le istruzioni pushMatrix(), translate() e popMatrix() che si trovano nel metodo show(). Questa sequenza di istruzioni ha lo scopo di salvare la posizione del vecchio sistema di riferimento (pushMatrix()), spostare l'origine nel punto x,y (translate(x,y)) e quindi, dopo aver disegnato l'oggetto, ripristinare il sistema di riferimento con l'origine nel punto in cui si trovava prima dello spostamento.

Ora veniamo al vero e proprio modello della legge di gravitazione universale (metodo update).

Il metodo ha due parametri di ingresso: il vettore g, ovvero la direzione del centro di massa rispetto al quale calcolare la forza, e il numero puro m, massa del centro di attrazione gravitazionale. Il primo passo è calcolare la direzione del vettore accelerazione come differenza vettoriale tra il vettore gravità (cioè la posizione del centro di attrazione gravitazionale) e il vettore posizione dell'oggetto. Fatto ciò, normalizziamo il vettore trovato affinché abbia modulo unitario e quindi lo moltiplichiamo scalarmente per l'inverso del quadrato della distanza e per le due masse in gioco. Per ricavare l'accelerazione, dovremo dividere per la massa dell'oggetto che stiamo aggiornando e questo mette in evidenza la ben nota indipendenza dell'accelerazione dalla massa dell'oggetto per la quale la calcoliamo.

A questo punto il gioco è fatto!

Manca soltanto la parte di codice che realizza la simulazione e usa la classe Ball.

Ball b1, b2;

void setup()
{
  size(1000, 800);

  background(255);
  b1 = new Ball(new PVector(width/2, height/2), new PVector(0, 0), new PVector(0, 0), 8000);
  b2 = new Ball(new PVector(width/2 - 200, height/3), new PVector(6, -2), new PVector(0, 0), 1);
}

void draw()
{
  //background(255);

  b1.update(b2.pos, b2.dim);
  b2.update(b1.pos, b1.dim);
  b1.show();
  b2.show();
}