< Home
Stampa

Animazioni

Sommario

In questa lezione useremo il canvas per svolgere una animazione in cui muoviamo una pallina sul canvas.

Moto uniforme

L’obiettivo è quello di muovere la pallina sul piano del canvas a velocità a costante, ovvero con moto rettilineo uniforme, e simulando urti elastici (cioè senza perdita di velocità) con le pareti.

Per prima cosa creiamo una struct con i dati della pallina, velocità, posizione

struct Ball {
  int size;
  float vx;
  float vy;
  float x;
  float y;
};

dove:

  • size: dimensione
  • vx e vy: velocità orizzontale e verticale
  • x e y: posizione

Creiamo le funzioni di servizio per disegnare la pallina:

Point getPosition(Ball* ball) {
  return Point(ball->x, ball->y);
}

void clear(Mat image) {
  rectangle( image,
         Point( 0, 0 ),
         Point( SIZE, SIZE),
         Scalar( 255, 255, 255 ),
         FILLED,
         LINE_8 );
}
  
void drawBall( Mat img, Ball* ball)
{
  circle( img,
      getPosition(ball),
      ball->size,
      Scalar( 0, 0, 0 ),
      FILLED,
      LINE_8 );
}

Dove SIZE è una costante che definisce la dimensione del canvas.

Ora che abbiamo definito le funzioni di disegno e la struttura dati che rappresenta la nostra pallina virtuale, andiamo a definire concettualmente come funziona l’animazione.

Una animazione è una attività che si basa su un ciclo infinito dove avvengono queste operazioni:

  1. Aggiornamento della posizione della pallina, in base alla velocità
  2. Aggiornamento del canvas
  3. Attesa fino alla prossima iterazione

In pratica la posizione ad ogni ciclo viene aggiornata, e poi resta immobile fino al successivo refresh. In pratica ad ogni ciclo viene generato un nuovo frame, e se il numero di frame per secondo è sufficientemente alto, l’utente ha la percezione di una animazione. Di norma il valore minimo per percepire un movimento fluido è di 24 frame per secondo, ovvero cicli della durata di 40ms, ma ovviamente maggiore il numero di frame migliore la fluidità.

Prima di vedere il ciclo vediamo la funzione di aggiornamento della posizione.

void calc(Ball* ball) {
  float newx = ball->x + ball->vx*1/FPS;
  float newy = ball->y + ball->vy*1/FPS;
  if (newx > SIZE-ball->size || newx < ball->size) {
    ball->vx = -ball->vx;    
  } else
  if (newy > SIZE-ball->size || newy < ball->size) {
    ball->vy = -ball->vy;    
  } else {
     ball->x = newx;
     ball->y = newy;
  }
}

Come si può vedere:

  • la posizione viene calcolata tenendo conto della posizione precedente e della velocità (orizzontale e verticale) moltiplicata per la durata del frame, secondo la legge oraria del moto:
x=x+vx×Δtx’ = x + v_x \times \Delta t
y=y+vy×Δty’ = y + v_y \times \Delta t
  • occorre verificare che la pallina non raggiunga la parete, altrimenti va invertita la direzione di velocità;
  • se non ci sono urti si aggiorna la posizione della pallina.

A questo punto possiamo passare al main, che conterrà una inizializzazione della pallina e poi il ciclo vero e proprio.

  srand(time(NULL));
  Mat img(SIZE, SIZE, CV_8UC1, Scalar(255, 255, 255));
  Ball* ball = new Ball;
  ball->x = HALF;
  ball->y = HALF;
  ball->size = BALLSIZE;
  float angle = M_PI_2 - rand() % 360 * (M_PI / 180);
  ball->vx = SPEED * cos(angle);
  ball->vy = SPEED * sin(angle);

Dove HALF è la posizione al centro del canvas, BALLSIZE la dimensione della pallina e SPEED la velocità della stessa.

Come si può vdedere l’angolo di direzione viene calcolato casualmente.

Per rappresentare invece le componenti orizzontale e verticale del movimento a partire dall’angolo, si procede mediante il calcolo trigonometrico di seno (per la componente verticale) e coseno (per quella orizzontale). Queste saranno alla base del calcolo della velocità verticale e di quella orizzontale:

vx=speed×cosαv_x = speed \times \cos \alpha
vy=speed×sinαv_y = speed \times \sin \alpha

Queste due variabili sono quelle poi utilizzate per calcolare il movimento in base alla legge oraria del moto vista sopra.

Scriviamo il ciclo principale:

while(1) {
    calc(ball);
    clear(img);
    drawBall(img, ball);
    imshow("Movimento", img);
    waitKey(1000/FPS);    
  }

delete(ball);
  waitKey(0);
  return(0);

Come si può vedere con nozioni minime di fisica, si può simulare il movimento con semplici animazioni.

Moto non uniforme

In questo esempio introduciamo la gravità, e la legge oraria del moto uniformemente accelerato:

  • La pallina si muove in modo costante lungo l’asse x, ma uniformemente accelerato lungo l’asse y. La legge oraria è infatti:
vx=vxv_x’ = v_x
vy=vy+G×Δtv_y’ = v_y + G \times \Delta t

Dove G rappresenta la costante di accelerazione (ad esempio la gravità).

Il calcolo della posizione usa la stessa formula dell’esercizio precedente.

La scelta delle costante G deve tenere conto sia del tempo di refresh del canvas, sia delle unità di misura. Se consideriamo un px come un mm, possiamo definire G = 980 (mm/s2), e un tempo di refresh di 1/50 di secondo

Veniamo al codice:

#define G 980
#define BALLSIZE 5
#define SPEED 1000
#define FPS 50

Riscriviamo quindi la funzione calc:

void calc(Ball *ball)
{
   float dt = 1.0 / FPS;   
   float newx = ball->x + ball->vx * dt;
   float newy = ball->y + ball->vy * dt;
   ball->vy += G * dt;
   if (newx > SIZE || newx < 0)
   {
      ball->vx = -ball->vx;
   }
   else if (newy > SIZE)
   {
      ball->vy = -ball->vy;
   }
   else
   {
      ball->x = newx;
      ball->y = newy;
   }
}

In questa versione vediamo che quando la pallina urta col “pavimento” inverte la sua posizione y.

Possiamo comunque ridurre un po’ la velocità a causa dell’urto:

Il main resta lo stesso, ciò che cambia è la funzione calc

void calc(Ball *ball)
{
   float dt = 1.0 / FPS;   
   float newx = ball->x + ball->vx * dt;
   float newy = ball->y + ball->vy * dt;
   ball->vy += G * dt;
   if (newx > SIZE || newx < 0)
   {
      ball->vx = -ball->vx * 0.9;
   }
   else if (newy > SIZE)
   {
      ball->vy = -ball->vy * 0.9;
   }
   else
   {
      ball->x = newx;
      ball->y = newy;
   }
}

In questo caso i rimbalzi saranno sempre meno forti fino alla fermata completa della pallina.

Conclusioni

In questa lezione abbiamo visto come sia possibile creare l’animazione di una pallina che si muove con moto uniforme oppure con moto non uniforme, sfruttando le leggi orarie del moto.