Animazioni
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, simulando urti perfetti (cioè senza perdita di velocità) con le pareti.
Per prima cosa creiamo una struct con i dati della pallina:
struct Ball {
int speed;
Point position;
float angle;
int size;
float vx;
float vy;
};dove speed e angle sono velocità (costante) ed angolo (iniziale) rispetto al sistema di riferimento, che ricordiamo avere l’origine in alto a sinistra. Size e position sono ovvi, mentre vx e vy le vediamo sotto.
Creiamo le due funzioni di servizio per disegnare la pallina:
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,
ball->position,
ball->size,
Scalar( 0, 0, 0 ),
FILLED,
LINE_8 );
}Siccome facciamo una animazione ci servirà un ciclo while infinito che ad ogni iterazione cancella il canvas (clear) e poi disegna la pallina nella nuova posizione.
Ma prima di disegnare la pallina dobbiamo calcolare la nuova posizione. L’algoritmo da sviluppare dovrà avere a che fare con le seguenti variabili:
- posizione attuale della pallina
- velocità
- posizione futura della pallina
- posizione delle pareti
Per calcolarle dobbiamo rappresentare la velocità (angolo e modulo) in termini vettoriali, cioè individuando una velocità lungo l’asse x ed una lungo l’asse x. Essa è:
- Vx = speed * cos(angle)
- Vy = speed * sin(angle)
In quanto ad ogni iterazione dovremo traslare la pallina di un certo numero di pixel in orizzontale e in verticale.
Inizializziamo quindi la pallina nel main come nel seguente esempio:
Ball* ball = new Ball;
ball->position = Point(HALF,HALF);
ball->size = 10;
ball->angle = M_PI_2 - rand() % 360 * (M_PI / 180);
ball->speed = 6;
ball->vx = ball->speed * cos(ball->angle);
ball->vy = ball->speed * sin(ball->angle);
e scriviamo il ciclo principale:
while(1) {
calc(ball);
clear(img);
drawBall(img, ball);
imshow("Movimento", img);
waitKey(30);
}
delete(ball);
waitKey(0);
return(0);Il cuore del movimento è quindi la funzione calc(ball).
void calc(Ball* ball) {
int newx = ball->position.x + ball->vx;
int newy = ball->position.y + ball->vy;
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->position.x = newx;
ball->position.y = newy;
}
}Vediamo passo passo il funzionamento:
- si calcola la nuova posizione semplicemente sommando x a Vx e y a Vy. La legge oraria del moto uniforme è in realtà x’ = x + Vx*t, dove t è il tempo, ma consideriamo t=1. Lo stesso vale per l’asse verticale.
- A questo punto verifichiamo se x’ (newx) è maggiore dell’ascissa SIZE (costante pari alla dimensione del canvas) meno la dimensione della pallina, o minore dell’ascissa 0 più la dimensione della pallina. Se lo è la componente di velocità Vx viene semplicemente invertita (la pallina torna indietro).
- Lo stesso vale per l’asse y e le velocità verticali.
- Infine viene aggiornata la posizione.
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’ = Vx (moto costante lungo l’asse X)
Vy’ = Vy + G*T (moto uniformemente accelerato, gravità per unità di tempo) - G è negativa quindi se vogliamo vedere la pallina fare un moto parabolico (salire e poi scendere) bisogna necessariamente dare una Vy iniziale positiva;
- il calcolo della nuova posizione deve tenere conto di T:
x’ = x + Vx*t
y’ = y + Vy*t - il sistema di riferimento è con l’origine in basso a sinistra quindi cartesiano, ma la rappresentazione sul canvas è con l’origine in alto a destra, quindi dobbiamo convertire la y:
y(canvas) = SIZE – y(cartesiano)
dove SIZE è l’altezza del canvas. - la scelta delle costanti G e T 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 (T=0.02).
Veniamo al codice:
#define SIZE 800
#define T 0.02
#define G -980Siccome posizione della pallina nel sistema cartesiano e posizione nel canvas sono differenti riscriviamo alcune funzioni dell’esempio precedente:
struct Position {
float x;
float y;
};
struct Ball {
float speed;
Position position;
float angle;
int size;
float vx;
float vy;
};
void drawBall( Mat img, Ball ball)
{
Point position = Point(ball.position.x, SIZE-ball.position.y);
circle( img,
position,
ball.size,
Scalar( 0, 0, 0 ),
FILLED,
LINE_8 );
}Come si vede si usa la struct Position per il sistema cartesiano, e la struct Point per la conversione a quello del canvas.
Il main resta lo stesso, ciò che cambia è la funzione calc
void calc(Ball *ball)
{
float newx = ball->position.x + ball->vx*T;
ball->vy += G*T;
float newy = ball->position.y + ball->vy*T;
if (newx > SIZE || newx < 0) {
ball->vx = -ball->vx;
} else
if (newy < ball->size) {
ball->vy = -ball->vy;
} else {
ball->position.x = newx;
ball->position.y = newy;
}
}dove si applica al codice quanto scritto sopra. Non serve un controllo nella parete superiore, la gravità farà scendere la pallina.
Testare con speed=1500, angle = 75*M_PI/180
Se si vuole dare un tocco di realismo, introducendo un urto non perfettamente elastico, è sufficiente decrementare la velocità ad ogni urto:
void calc(Ball *ball)
{
float newx = ball->position.x + ball->vx*T;
ball->vy += G*T;
float newy = ball->position.y + ball->vy*T;
if (newx > SIZE || newx < 0) {
ball->vx = -ball->vx*0.9;
} else
if (newy < ball->size) {
ball->vy = -ball->vy*0.9;
} else {
ball->position.x = newx;
ball->position.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.
