Il mondo è regolato da formule matematiche che ne descrivono tutti gli eventi che si verificano su di esso. Anche la corsa di un uomo può essere rappresentata mediante formule matematiche. L'articolo mostra come, in maniera semplice, possano essere tradotti in numeri tutti i movimenti che compie un corridore per correre. Le possibili applicazioni di tali tecniche sono infinite: si passa dalla robotica alle simulazioni, dalla creazione di un modello matematico alla preparazione di prototipi 3D. In ogni caso, la matematica è sempre presente, a far da padrona su tutto.
Introduzione
Da quando utilizzo i software di regressione lineare e di curve fitting, ho sempre cercato di descrivere matematicamente qualsiasi evento che si verificasse intorno a me. Applicazioni ludiche o importanti sono diventate l'oggetto dei miei studi, alla ricerca di una possibile formula o relazione matematica che le governasse. Una di queste è rappresentata dalla corsa, disciplina a me molto cara. Quando vedo un corridore che corre (o, in generale, i movimenti che esegue il corpo umano per armonizzare al meglio le posizioni) la prima cosa che penso è quella di rappresentare matematicamente tale fatto. Gli attori che intervengono nella corsa, ovviamente, sono diversificati e l'aritmetica deve gestire molteplici parametri. Consapevole, dunque, nel fatto di utilizzare diverse formule, mi sono messo alla ricerca e allo studio di un metodo che potesse digitalizzare il movimento di una persona che corre. Vediamo, dunque, quali sono le fasi preliminari da intraprendere e quali le giuste metodologie da seguire. Pianificando bene il lavoro, l'applicazione finale risulterà, sicuramente, molto interessante, affascinante e, perché no, anche divertente.
Le fasi da seguire
Per raggiungere l'obbiettivo di riprodurre i movimenti della corsa al PC, dobbiamo seguire alcune fasi, alcune delle quali abbastanza critiche e faticose. La massima precisione è richiesta sia nell'applicazione delle formule ricavate sia nelle metodologie utilizzate. Intraprendiamo adesso le fasi per raggiungere lo scopo prefissato, in questa fantastica avventura a sfondo grafico e matematico.
Acquisizione del video
Per questa fase, le soluzioni sono molteplici. Si potrebbe riprendere, con la propria videocamera, la corsa di un parente o di un amico. L'ideale sarebbe di seguire lateralmente la persona che corre, con una sorta di carrello scorrevole. Un'altra buona soluzione è quella di reperire il filmato da Internet. Il video non deve durare molto. E' sufficiente riprendere la sequenza dei fotogrammi in cui il corridore si muove per un periodo completo, ossia da quando il corpo umano assume una certa posizione a quando, dopo aver compiuto due passi, si ritrova nella medesima posizione. Ma procediamo con calma e vediamo quali sono i lavori da compiere. Per il risultato finale vedremo come saranno necessari parecchi tools da utilizzare. Per acquisire il filmato dobbiamo rispettare alcuni accorgimenti, al fine di salvaguardare la qualità dell'intera operazione. A grandi linee il video dovrà possedere le seguenti caratteristiche tecniche e visive:
- deve risultare di buona qualità;
- il corridore deve essere ripreso lateralmente;
- possibilmente l'angolo di ripresa non dovrebbe cambiare;
- il personaggio dovrebbe essere il più vicino possibile.
Dopo aver fatto una breve ricerca, il video corrispondente ai nostri criteri è stato trovato su Youtube a questo indirizzo. Si può rintracciarlo sullo stesso sito con la chiave di ricerca "Assoluti Indoor Ancona 2014". Si tratta di una prova di salto in lungo, nella quale l'atleta è ben in evidenza, con le (quasi) corrette angolazioni di ripresa per i nostri scopi, come visibile in Figura 1. A seconda della tipologia di video, potrebbe essere necessario eliminare l'overlay. I casi sono molti e cambiano sia dal tipo di filmato utilizzato sia dalla codifica adottata. Il download di un video su PC può essere effettuato attraverso diversi servizi messi a disposizione nella Rete. Uno di questi è Keepvid.
Taglio del video
Come detto in precedenza, il video potrebbe risultare eccessivamente lungo e caratterizzato dalla presenza di scene inutili e non pertinenti all'esperimento che stiamo andando ad attuare. Occorre, pertanto, effettuare un drastico taglio, in modo da prendere solo la sequenza utile e necessaria. La porzione selezionata nell'esempio inizia non appena la punta del piede sinistro dell'atleta lascia terra e termina dopo due passi, con l'immagine equivalente, come mostrato in Figura 2. Per la precisione, si tratta dei fotogrammi compresi tra il 1014 e il 1131. Purtroppo, fenomeni di prospettiva potrebbero alterare un tantino i calcoli effettuati. Per questo motivo, come detto in precedenza, l'ideale sarebbe utilizzare un carrello mobile su binari che segua il corridore, in una sorta di sistema a brandeggio. L'operazione di taglio può essere eseguita con qualsiasi programma di video-editing. Nell'esempio, abbiamo utilizzato il potente software Virtualdub, con cui abbiamo mantenuta intatta la sequenza sopra descritta, eliminando tutto il resto.
La Figura 3, in una piccola miniatura animata, mostra il filmato dei soli fotogrammi estrapolati. Esso sarà utile per andare a ricavare le posizioni degli elementi del corridore (braccia, gambe, etc).
Individuazione del soggetto che corre
Lungi dal riprodurre a PC un personaggio dalle sembianze umane, lo scopo è quello di simulare il movimento di uno pseudo-robot composto semplicemente da alcuni segmenti. A noi interessa, infatti, l'aspetto matematico della problematica, non tanto quello grafico. Il corridore digitale deve essere costruito, dunque, con tante linee, che rappresentano le braccia, le gambe, le cosce, etc. La Figura 4 mostra gli elementi del corridore, che saranno poi processati, calcolati e posizionati dal software creato successivamente ad-hoc. Per semplicità del progetto si è preferito prevedere la presenza dei soli segmenti principali. Nella illustrazione possiamo osservare i seguenti elementi:
- Testa: ovviamente un'ellisse per il capo;
- B1, B2: segmenti delle braccia;
- A1, A2: segmenti degli avambracci;
- M1, M2: segmenti delle mani;
- Tronco: segmento del tronco;
- C1, C2: segmenti delle cosce;
- G1, G2: segmenti delle gambe;
- P1, P2: segmenti dei piedi.
Determinazione delle coordinate dei segmenti (coordinate assolute)
Questa è, probabilmente, la fase più critica dell'intera operazione e da essa dipende la bontà del lavoro finale. Il suo scopo è quello di ricavare, fotogramma per fotogramma, tutte le posizioni dei segmenti che compongono il corpo umano. Il nostro esempio prevede la presenza di 24 fotogrammi. Per ognuno di essi dovremo determinare la posizione di ogni elemento, in particolare:
- coordinata X e Y di inizio della linea;
- coordinata X e Y di fine della linea;
- la testa sarà considerata alla stregua di un cerchio, con X e Y unici e rappresentanti il centro con un raggio di lunghezza opportuna.
Il calcolo è eseguito considerando le coordinate delle parti mobili relative a due elementi fissi: la testa ed il tronco. Par valutare queste distanze utilizziamo il programma di grafica Photofiltre (free) che, in tempo reale, mostra la posizione del mouse (in pixel) sulla barra di stato. Utilizzeremo tale informazione per ricavare le varie coordinate delle linee, dopo che i 24 fotogrammi del filmato sono stati esportati come singole immagini, per poter essere aperte ad una ad una con il programma grafico. Come procedere? La Figura 5 illustra il metodo: per ogni coordinata delle linee (X, Y iniziali e X, Y finali) occorre stabilire la posizione, tramite l'uso del mouse. Da tale posizione sarà successivamente sottratta quella della testa, considerata come posizione zero. E' un lavoro un pò lungo dove è richiesta la massima precisione e tanta pazienza. Alla fine dei calcoli è possibile inserire i risultati in una tabella elettronica. In alcuni fotogrammi, le parti mobili di sinistra sono nascoste e coperte dal resto del corpo. Per queste, si è proceduto, per la determinazione delle posizioni, ad intuito, effettuando una sorta di "interpolazione mentale". Per la parte di destra, invece, non vi sono stati problemi di sorta, dal momento che la telecamera la inquadrava completamente.
Come si vede dalla Figura 5, le coordinate assolute X del braccio sono 466 (inizio linea) e 486 (fine linea). Basta sottrarre la posizione della testa per ottenere le coordinate relative (18 e 38). Le informazioni così ricavate sono state scritte in un enorme foglio elettronico, composto da ben 55 colonne, come mostrato in Figura 6. Ricordiamo che ancora il documento contiene le coordinate assolute delle varie posizioni del corpo. La Figura 7 visualizza, invece, un particolare più leggibile di tale foglio di lavoro.
Calcolo delle coordinate relative dei segmenti
Le posizioni appena calcolate sono assolute, rispetto al fotogramma. Se manteniamo tale criterio, il movimento risultante sarà estremamente incostante e impreciso e, soprattutto, non potrà essere riprodotto in un loop. Le varie posizioni determinate in precedenza, invece, devono riferirsi ad un punto solidale e costante, sia come coordinata X sia come coordinata Y. Per questo punto si è preferito scegliere la testa.
Occorre, allo scopo, creare un secondo foglio di lavoro contenente, stavolta, tutte le posizioni ricavate in precedenza ma sottratte da quelle della testa. In questa maniera si ottengono le posizioni relative, successivamente gestibili più facilmente, senza problemi di sorta.
Prova delle coordinate
Al fine di verificare la correttezza delle coordinate acquisite, proviamo a "dare in pasto" al PC tutti i numeri ed eseguire i movimenti mediante riproduzione sequenziale dei fotogrammi. Per raggiungere lo scopo affidiamoci a Processing, un linguaggio di programmazione dedicato alla grafica, di cui abbiamo anche parlato in altri precedenti articoli (un interessante articolo è disponibile a questo indirizzo). Il metodo consiste nel memorizzare tutte le coordinate X e Y in appositi vettori. In un ciclo infinito, quindi, si estrapolano gli elementi ed il loro valore viene fornito ai comandi grafici per tracciare le linee ed i cerchi. Il numero degli elementi dei vettori è, ovviamente, equivalente al numero dei fotogrammi del filmato. Il listato sottostante costituisce il sorgente "a forza bruta" per la riproduzione dei movimenti del corridore. La Figura 8 illustra l'ambiente di programmazione di Processing.
float ADDX; float ADDY; int k; //---------------------------- int[] BraccioSxX1 = {14,11,8,10,11,14,15,12,8,2,2,-26,-24,-28,-34,-34,-44,-52,-48,-51,-51,-48,-45,-55}; int[] BraccioSxY1 = {26,23,23,24,26,27,30,35,37,38,38,35,34,34,40,44,40,43,43,45,55,62,61,58}; int[] BraccioSxX2 = {36,39,34,30,27,25,19,9,1,-5,-15,-45,-50,-55,-64,-68,-72,-70,-61,-57,-39,-30,-27,-30}; int[] BraccioSxY2 = {54,54,54,58,61,67,72,73,76,84,68,42,35,37,45,57,69,83,87,77,100,103,97,92}; //---------------------------- int[] AvamBraccioSxX1 = {36,39,34,30,27,25,19,9,1,-5,-15,-45,-50,-55,-64,-68,-72,-70,-61,-57,-39,-30,-27,-30}; int[] AvamBraccioSxY1 = {54,54,54,58,61,67,72,73,76,84,68,42,35,37,45,57,69,83,87,77,100,103,97,92}; int[] AvamBraccioSxX2 = {38,38,39,43,46,49,36,17,-17,-24,-31,-49,-57,-63,-69,-68,-56,-49,-31,-11,-4,-3,-11,-23}; int[] AvamBraccioSxY2 = {23,22,27,48,69,93,105,98,108,109,88,76,69,70,81,98,107,109,119,111,98,81,70,64}; //---------------------------- int[] ManoSxX1 = {38,38,39,43,46,49,36,17,-17,-24,-31,-49,-57,-63,-69,-68,-56,-49,-31,-11,-4,-3,-11,-23}; int[] ManoSxY1 = {23,22,27,48,69,93,105,98,108,109,88,76,69,70,81,98,107,109,119,111,98,81,70,64}; int[] ManoSxX2 = {39,36,34,49,60,60,40,23,41,-34,-47,-55,-60,-67,-71,-63,-49,-39,-12,12,10,11,-5,-25}; int[] ManoSxY2 = {6,6,15,38,76,108,126,115,117,114,98,92,86,87,96,109,117,117,122,109,88,65,48,49}; //---------------------------- int[] BraccioDxX1 = {-21,-25,-29,-31,-29,-26,-22,-29,-29,-29,-28,-20,-15,-19,-24,-30,-32,-37,-34,-39,-46,-55,-63,-78}; int[] BraccioDxY1 = {28,30,29,31,35,37,41,45,46,51,49,46,42,42,42,43,45,50,51,52,55,61,62,66}; int[] BraccioDxX2 = {-64,-66,-68,-70,-63,-56,-47,-33,-17,-7,3,9,12,9,0,-11,-21,-26,-39,-52,-71,-90,-108,-121}; int[] BraccioDxY2 = {46,42,44,53,60,70,84,94,93,89,83,73,67,66,65,74,81,93,97,97,98,93,85,79}; //---------------------------- int[] AvamBraccioDxX1 = {-64,-66,-68,-70,-63,-56,-47,-33,-17,-7,3,9,12,9,0,-11,-21,-26,-39,-52,-71,-90,-108,-121}; int[] AvamBraccioDxY1 = {46,42,44,53,60,70,84,94,93,89,83,73,67,66,65,74,81,93,97,97,98,93,85,79}; int[] AvamBraccioDxX2 = {-80,-85,-84,-77,-63,-42,-18,3,23,28,31,28,31,34,28,32,17,3,-23,-48,-76,-96,-118,-129}; int[] AvamBraccioDxY2 = {82,83,86,91,105,114,116,112,95,77,58,46,36,34,49,66,94,116,132,138,144,133,130,123}; //---------------------------- int[] ManoDxX1 = {-80,-85,-84,-77,-63,-42,-18,3,23,28,31,28,31,34,28,32,17,3,-23,-48,-76,-96,-118,-129}; int[] ManoDxY1 = {82,83,86,91,105,114,116,112,95,77,58,46,36,34,49,66,94,116,132,138,144,133,130,123}; int[] ManoDxX2 = {-89,-91,-91,-80,-57,-33,2,21,41,43,40,32,33,38,39,49,37,15,-17,-50,-84,-102,-124,-134}; int[] ManoDxY2 = {103,103,107,118,127,131,126,115,88,61,36,18,13,17,40,62,95,129,153,160,161,153,150,145}; //---------------------------- int[] TroncoX1 = {-3,-6,-9,-8,-7,-4,-1,-4,-8,-11,-12,-13,-16,-17,-23,-27,-33,-37,-35,-38,-40,-47,-52,-60}; int[] TroncoY1 = {19,21,19,19,22,22,28,26,30,30,32,32,34,34,33,36,37,37,41,44,44,50,49,52}; int[] TroncoX2 = {-9,-13,-18,-29,-19,-16,-17,-11,-13,-18,-13,-10,-10,-15,-30,-37,-43,-41,-49,-55,-57,-65,-69,-66}; int[] TroncoY2 = {111,106,107,108,105,104,110,110,109,114,116,112,113,116,124,115,121,128,130,128,132,135,144,134}; //---------------------------- int[] CosciaSxX1 = {-9,-13,-18,-29,-19,-16,-17,-11,-13,-18,-13,-10,-10,-15,-30,-37,-43,-41,-49,-55,-57,-65,-69,-66}; int[] CosciaSxY1 = {111,106,107,108,105,104,110,110,109,114,116,112,113,116,124,115,121,128,130,128,132,135,144,134}; int[] CosciaSxX2 = {-43,-50,-48,-45,-38,-24,-7,14,21,35,40,47,49,39,34,9,7,-4,-14,-30,-36,-53,-73,-88}; int[] CosciaSxY2 = {154,151,153,157,161,167,170,169,165,155,145,138,134,134,142,152,161,171,178,177,186,196,200,199}; //---------------------------- int[] GambaSxX1 = {-43,-50,-48,-45,-38,-24,-7,14,21,35,40,47,49,39,34,9,7,-4,-14,-30,-36,-53,-73,-88}; int[] GambaSxY1 = {154,151,153,157,161,167,170,169,165,155,145,138,134,134,142,152,161,171,178,177,186,196,200,199}; int[] GambaSxX2 = {-97,-107,-117,-110,-100,-79,-59,-45,-36,-40,-18,-3,23,37,43,39,31,18,-5,-31,-62,-96,-129,-150}; int[] GambaSxY2 = {199,184,168,154,143,140,141,144,148,156,170,184,193,201,208,218,228,239,243,254,255,249,247,239}; //---------------------------- int[] PiedeSxX1 = {-97,-107,-117,-110,-100,-79,-59,-45,-36,-40,-18,-3,23,37,43,39,31,18,-5,-31,-62,-96,-129,-150}; int[] PiedeSxY1 = {199,184,168,154,143,140,141,144,148,156,170,184,193,201,208,218,228,239,243,254,255,249,247,239}; int[] PiedeSxX2 = {-99,-118,-135,-137,-131,-114,-96,-78,-66,-50,-22,13,43,66,75,73,66,54,29,-3,-36,-74,-110,-149}; int[] PiedeSxY2 = {231,214,194,174,160,150,148,154,171,185,205,217,216,217,218,222,228,234,254,252,264,267,267,269}; //---------------------------- int[] CosciaDxX1 = {-9,-13,-18,-29,-19,-16,-17,-11,-13,-18,-13,-10,-10,-15,-30,-37,-43,-41,-49,-55,-57,-65,-69,-66}; int[] CosciaDxY1 = {111,106,107,108,105,104,110,110,109,114,116,112,113,116,124,115,121,128,130,128,132,135,144,134}; int[] CosciaDxX2 = {45,45,38,32,29,21,14,2,-13,-26,-43,-50,-57,-60,-60,-62,-59,-47,-30,-24,-3,-1,-1,-1}; int[] CosciaDxY2 = {123,126,130,142,150,154,161,167,178,183,183,175,177,179,188,187,194,197,197,190,179,173,171,177}; //---------------------------- int[] GambaDxX1 = {45,45,38,32,29,21,14,2,-13,-26,-43,-50,-57,-60,-60,-62,-59,-47,-30,-24,-3,-1,-1,-1}; int[] GambaDxY1 = {123,126,130,142,150,154,161,167,178,183,183,175,177,179,188,187,194,197,197,190,179,173,171,177}; int[] GambaDxX2 = {23,40,51,53,52,35,19,-9,-37,-70,-93,-110,-121,-131,-128,-127,-119,-104,-92,-77,-62,-43,-32,0}; int[] GambaDxY2 = {188,195,205,210,219,225,238,240,240,234,225,221,208,193,183,175,172,177,182,195,207,223,237,241}; //---------------------------- int[] PiedeDxX1 = {23,40,51,53,52,35,19,-9,-37,-70,-93,-110,-121,-131,-128,-127,-119,-104,-92,-77,-62,-43,-32,0}; int[] PiedeDxY1 = {188,195,205,210,219,225,238,240,240,234,225,221,208,193,183,175,172,177,182,195,207,223,237,241}; int[] PiedeDxX2 = {45,68,82,86,84,67,51,21,-13,-46,-83,-113,-131,-148,-159,-155,-146,-130,-111,-84,-59,-25,6,28}; int[] PiedeDxY2 = {205,204,205,206,217,229,248,253,255,253,254,254,241,225,204,193,193,198,215,226,238,251,253,248}; //---------------------------- void setup() { size(340, 330); } //---------------------------- void draw() { ADDX=170+(k*2.6); ADDY=60-(k*1.1); background(102); //----Testa----- fill(255, 190, 166); noStroke(); ellipse(TroncoX1[k]+ADDX+5,TroncoY1[k]+ADDY-35,50,50); //------Occhio----- fill(0,0,0); noStroke(); ellipse(TroncoX1[k]+ADDX+20,TroncoY1[k]+ADDY-45,7,5); //------Naso----- fill(0,0,0); noStroke(); ellipse(TroncoX1[k]+ADDX+28,TroncoY1[k]+ADDY-33,2,2); //-----Bocca----- strokeWeight(1); stroke(30,0,0); line(TroncoX1[k]+ADDX+15,TroncoY1[k]+ADDY-25,TroncoX1[k]+ADDX+27,TroncoY1[k]+ADDY-23); //----Arto superiore SX----- strokeWeight(4); stroke(255, 190, 166); line(BraccioSxX1[k]+ADDX,BraccioSxY1[k]+ADDY,BraccioSxX2[k]+ADDX,BraccioSxY2[k]+ADDY); strokeWeight(3); stroke(255, 180, 166); line(AvamBraccioSxX1[k]+ADDX,AvamBraccioSxY1[k]+ADDY,AvamBraccioSxX2[k]+ADDX,AvamBraccioSxY2[k]+ADDY); strokeWeight(2); stroke(205, 150, 126); line(ManoSxX1[k]+ADDX,ManoSxY1[k]+ADDY,ManoSxX2[k]+ADDX,ManoSxY2[k]+ADDY); //----Arto superiore DX----- strokeWeight(4); stroke(255, 190, 166); line(BraccioDxX1[k]+ADDX,BraccioDxY1[k]+ADDY,BraccioDxX2[k]+ADDX,BraccioDxY2[k]+ADDY); strokeWeight(3); stroke(255, 180, 166); line(AvamBraccioDxX1[k]+ADDX,AvamBraccioDxY1[k]+ADDY,AvamBraccioDxX2[k]+ADDX,AvamBraccioDxY2[k]+ADDY); strokeWeight(2); stroke(205, 150, 126); line(ManoDxX1[k]+ADDX,ManoDxY1[k]+ADDY,ManoDxX2[k]+ADDX,ManoDxY2[k]+ADDY); //-----Tronco------ strokeWeight(5); stroke(0, 0, 120); line(TroncoX1[k]+ADDX,TroncoY1[k]+ADDY,TroncoX2[k]+ADDX,TroncoY2[k]+ADDY); //----Arto inferiore SX----- strokeWeight(5); stroke(0, 0, 120); line(CosciaSxX1[k]+ADDX,CosciaSxY1[k]+ADDY,CosciaSxX2[k]+ADDX,CosciaSxY2[k]+ADDY); strokeWeight(4); stroke(255, 190, 166); line(GambaSxX1[k]+ADDX,GambaSxY1[k]+ADDY,GambaSxX2[k]+ADDX,GambaSxY2[k]+ADDY); strokeWeight(3); stroke(0, 0, 160); line(PiedeSxX1[k]+ADDX,PiedeSxY1[k]+ADDY,PiedeSxX2[k]+ADDX,PiedeSxY2[k]+ADDY); //----Arto inferiore DX----- strokeWeight(5); stroke(0, 0, 120); line(CosciaDxX1[k]+ADDX,CosciaDxY1[k]+ADDY,CosciaDxX2[k]+ADDX,CosciaDxY2[k]+ADDY); strokeWeight(4); stroke(255, 190, 166); line(GambaDxX1[k]+ADDX,GambaDxY1[k]+ADDY,GambaDxX2[k]+ADDX,GambaDxY2[k]+ADDY); strokeWeight(3); stroke(0, 0, 160); line(PiedeDxX1[k]+ADDX,PiedeDxY1[k]+ADDY,PiedeDxX2[k]+ADDX,PiedeDxY2[k]+ADDY); delay(100); k=k+1; if(k==24) k=0; }
La Figura 9 mostra il risultato animato del listato di cui sopra. Si noti come i movimenti armonici corrispondano perfettamente a quelli del personaggio originale. Le posizioni dell'uomo, ricordiamolo, sono attinte da tanti vettori.
Aggiungendo il seguente pezzo di codice, si può inserire nella scena un albero che contribuisce ad aumentare il livello di realtà dell'animazione, dal momento che esso si muove nello sfondo, come mostrato in Figura 10.
//----Albero---- noStroke(); fill(106, 43, 0); rect(400-k*20-20, 50, 50, 200); fill(0, 190, 0); noStroke(); ellipse(400-k*20,60,130,80);
ATTENZIONE: quello che hai appena letto è solo un estratto, l'Articolo Tecnico completo è composto da ben 5120 parole ed è riservato agli ABBONATI. Con l'Abbonamento avrai anche accesso a tutti gli altri Articoli Tecnici che potrai leggere in formato PDF per un anno. ABBONATI ORA, è semplice e sicuro.
Caro Giovanni Di Maria, sono rimasta molto colpita dal tuo articolo sia per la parte matematica che per quella informatica. Mentre leggevo il listato del programma, mi aspettavo di incontrare funzioni periodiche poi, ho riletto meglio l’articolo e ho capito che non erano necessarie. Tempo fa avevo simulato il movimento di un omino in javascript e avevo utilizzato funzioni periodiche per il movimento delle braccia e delle gambe…naturalmente era qualcosa di molto ma molto arcaico. Voglio provare il tuo procedimento con javascript, anche perchè la sintassi mi sembra la stessa. Grazie