Movimento realistico

dai filtri digitali


Non mi assumo nessuna responsabilita' per danneggiamenti, perdita di dati o danni personali come risultato diretto o indiretto dell'uso delle informazioni contenute in queste pagine. Questo materiale e' fornito cosi' com'e' senza nessuna garanzia implicita o esplicita.


Home
Hardware
Software

La curiosita'

E' possibile ottenere un movimento che sembri naturale attraverso un filtro numerico?
Ovvero: sarebbe possibile smussare un movimento a gradini in modo tale che possa sembrare piu' naturale?
Questa e' l'idea che mi e' venuta: se fosse possibile realizzare un filtro numerico in grado di smussare un segnale a gradini o, meglio ancora, di mimare il comportamento di un sistema fisico, allora potrei ottenere un movimento fluido che sembri naturale riducendo al massimo i conti stessi per ottenerlo.

Se volete vi mostro che cosa ho scoperto e fin dove sono arrivato.

Principio di funzionamento

general system

Consideriamo un generico sistema S, questo riceve degli stimoli in ingresso e reagisce secondo la propria funzione di trasferimento. Consideriamo un sistema costituito da una massa m che striscia con attrito controllato su di un piano, colleghiamo tramite una molla tarata k la massa ad un corsore c che scorre lungo una rotaia parallela al piano orizzontale. Il sistema si trova in condizioni di riposo e sia la massa m che il carrello c si trovano alla stessa quota. La variabile d'ingresso del nostro sistema sara' la posizione del carrello: questo viene spostato in modo istantaneo e vincolato saldamente alla sua nuova posizione, in modo tale che la molla non possa spostarlo ma possa agire solo sulla massa m. La variabile di uscita sara' ovviamente la posizione della massa m.

system example

Quando perturbiamo il sistema, ovvero spostiamo il carrello, ad esempio di 10cm, la massa m si mette in movimento sotto l'azione della forza della molla k tentando di raggiungere la posizione del carrello. Nel fare questo il movimento della massa e' naturale. Il moto viene controllato agendo sul coefficiente di attrito e sulla massa m stessa: alzando troppo il coefficiente di attrito la massa potrebbe non muoversi per niente o tendere a fatica alla posizione finale, abbassando il coefficiente di attrito la massa m potrebbe mettersi ad oscillare attorno alla posizione finale; il tutto in modo naturale.

In linea di principio si potrebbe scrivere la trasformata di Laplace di questo sistema, poi tradurre la trasformata di Laplace nella trasformata z corrispondente e in questo modo ottenere la versione numerica del nostro sistema.

Questo approccio sarebbe sicuramente interessante, ma almeno per il momento suggerisco una via piu' semplice: decidiamo in modo quasi arbitrario che il sistema sia del secondo ordine, quindi scriviamo direttamente la trasformata z di un sistema del secondo ordine, questo sembra piu' semplice che passare per Laplace.

Una precisazione mi sembra dovuta: il sistema descritto sopra e' veramente del secondo ordine, quindi non stiamo barando troppo; in realta' il trucco sta nel come vengono poste le cose, il sistema sopra descritto e' si del secondo ordine, ma cosi' come e' descritto, inserendo altri effetti come la resistenza dell'aria o lo stiramento della molla l'ordine del sistema aumenta. Per il momento possiamo dire che l'approssimazione e' piu' che sufficiente.

Filtro numerico

Il sistema descritto sopra ha certamente un comportamento passa-basso (sfido chiunque a dire che sia un passa-alto), quindi scriviamo la trasformata z di un filtro passa-basso del secondo ordine ed abbiamo vinto. In ingresso avremo la posizione del carrello, in uscita avremo la posizione instantanea della massa m.

La figura sottostante mostra in rosso la risposta al gradino di un semplice filtro RC (quindi del primo ordine). La risposta del filtro e' immediata, io vorrei invece una risposta arrotondata anche verso il basso, come la linea verde; questo perche' la massa m all'inizio ci deve mettere del tempo per mettersi in moto, acquistando sempre piu' velocita' per poi rallentare in prossimita' dell'arrivo.

risposta voluta

Questo giustifica il fatto che il nostro filtro digitale non possa essere del primo ordine.

Serve fare una puntualizzazione: anche scegliendo un sistema del secondo ordine non e' possibile scegliere la dimensione del ginocchio che la curva presenta alla partenza, ogni sistema e' cio' che e', di fatto i sistemi del secondo ordine presentano questa gobbetta alla partenza, ma non possiamo in alcun modo esaltarla se non cambiando l'ordine del sistema; per il momento io mi accontento del secondo grado.

Quindi secondo grado sia, ma di che tipo?
Con un po' di sadismo in corpo questa volta punteremo ad un filtro di tipo IIR che ovviamente avra' due poli complessi coniugati (al piu' reali coincidenti). Qui sotto vediamo la posizione dei poli nel piano complesso di alcuni filtri passa-basso.

zplane view

Qui sotto vediamo il corrispondente andamento in frequenza. Un filtro cosi' fatto puo' avere solo due configurazioni: due poli reali, al piu' coincidenti, o due poli complessi coniugati. Nel primo caso la risposta assomiglia molto alla risposta di un filtro RC del primo ordine, la risposta puo' essere molto lunga. Nel secondo caso invece il sistema presenta una pulsazione naturale alla quale tende a rispondere. Nella figura sotto la linea rossa apprtiene ad un filtro che ha due poli reali, le linee verde e blu invece appartengono a due filtri che hanno due poli complessi coniugati, nel caso della linea blu i poli sono piu' vicini alla circonferenza unitaria. Come si vede, piu' i poli si avvicinano alla circonferenza unitaria e piu' compare in frequenza una frequenza di risonanza: la linea blu mostra un guadagno superiore a 0dB per le frequenze comprese tra 0.02 e 0.03, questo sistema ha una risposta piu' veloce ma tende ad oscillare attorno alla posizione finale. In generale piu' si avvicinano i poli alla circonferenza unitaria e piu' si riduce lo smorzamento del sistema, nell'esempio di prima della massa m, stiamo riducendo l'attrito con il piano. Se i poli si trovassero sulla circonferenza unitaria lo smorzamento sarebbe nullo, come l'attrito, e l'oscillazione una volta avviata non si fermerebbe piu'. In queste condizioni la risposta in frequenza diverge alla frequenza di risonanza. La frequenza di risonanza dipende invece dall'argomento dei poli: cioe' dall'angolo che formano rispetto all'asse reale.

freq. resp.

La risposta in frequenza da sola puo' non bastare a chiarire la situazione, quindi ecco le risposte al gradino dei tre filtri fisti sopra: come si vede la riga rossa tende asintoticamente all'ingresso come farebbe un semplice filtro RC, il verde ha una leggera sovraelongazione prima di tendere all'ingresso, infine il sistema blu tende ad oscillare nel tentativo di raggiungere il valore finale. L'andamento scalettato e' dato dal fatto che il risultato viene di proposito arrotondato ad un intero.

time resp.

L'implementazione

Ho racchiuso tutto questo in un modulo C, in modo che sia facilmente includibile in qualsiasi progetto; con molta fantasia l'ho chiamato Fluid, potete scaricarlo qui. Fluid e' un filtro configurabile concepito per produrre il movimento degli sprite in modo realistico partendo da un ingresso a gradini, contiene un paio di accorgimenti mirati proprio al movimento di sprite e altri oggetti simili.

Abbiamo visto che la posizione dei poli determina la risposta del filtro e quindi il tipo di movimento che avra' il nostro oggetto, in questo consiste la configurabilita' di Fluid. La configurazione avviene attraverso la funzione:

  void Fluid_Create(FluidData_t *Data,float r,float fi,Fluid_IO_t Pos);
      

Dove r e fi sono le coordinate polari di uno dei poli complessi coniugati, fi e' espresso in gradi per semplicita'; nel caso di poli reali (fi = 0), questi saranno coincidenti. Pos e' la posizione iniziale dell'oggetto, questa deve essere caricata nella storia del filtro; infine Data rappresenta il filtro vero e proprio che viene inizializzato.

Una volta creato il filtro ed avviato il movimento vorremmo che questo terminasse, sembra banale ma potrebbe non esserlo del tutto: i filtri IIR hanno la brutta caratteristica di avere una risposta infinita, la risposta tende asintoticamente alla posizione finale senza mai raggiungerla. Visto che per la nostra applicazione potrebbe essere disastroso ho deciso di porre un limite temporale al movimento stesso: dopo un numero prefissato di passi la posizione finale viene comunque forzata e mantenuta. Questa caratteristica di Fluid potrebbe produrre un salto alla fine del movimento, niente paura: fa tutto parte del gioco; ho pensato che un possibile salto alla fine del movimento fosse meno disastroso di un movimento che non ha mai fine per problemi di approssimazioni.

  void Fluid_Start(FluidData_t *Data,Fluid_IO_t TotalSteps,Fluid_IO_t EndPos);

  Fluid_IO_t Fluid_Move(FluidData_t *Data,Fluid_IO_t Input);
      

La prima delle due funzioni inizializza il movimento, TotalSteps indica i passi totali: dopo questo numero di reiterazioni del filtro la posizione finale EndPos viene forzata e mantenuta. La funzione Fluid_Move() produce le vere uscite del filtro: questa viene chiamata N volte ad intervalli regolari per ottenre tutte le posizioni intermendie occupate dal nostro sprite, nel caso di ingresso a gradino Input ha lo stesso valore di EndPos; questa condizione non e' un vincolo.

Facciamo un esempio per capire meglio: riprendiamo alcuni dei valori visti in precedenza, poniamo r=0.90 e fi=7 gradi.

  FluidData_t		FluidFilter;


  Fluid_Create(&FluidData,0.90,7.0,0);
  Fluid_Start(&FluidData,35,100);
  for(i=0;i<70;i++)
    Position=Fluid_Move(&FluidFilter,100);
      

Il risultato prodotto da questo codice lo possiamo vedere qui sotto.

example

Come potevamo aspettarci si tratta della risposta disegnata in verde negli esempi visti sopra, dopotutto abbiamo utilizzato gli stessi parametri: 0.90 e 7.0; questa volta pero' c'e' un'improvvisa interruzione all'istante 35.

  Fluid_Start(&FluidData,35,100);
      

La riga qui sopra dichiara che il movimento puo' iniziare, durera' un massimo di 35 passi, dopo di che la posizione finale sara' 100. Dopo il 35-esimo passo Fluid forza l'uscita del filtro a 100 qualsiasi sia lo stato del filtro, questa condizione permane nel tempo fino alla prossima chiamata alla funzione Fluid_Start(). Per avere una terminazione senza salti servirebbe di stimare la durata naturale del movimento, in modo che il gradino finale risulti impercettibile.

Ok, ora e' tutto chiaro, non ci resta che produrre una coppia di numeri, r e fi, che determina il tipo di movimento, questo puo' avere un andamento asintotico alla posizione finale oppure sovraelongare con oscillazioni piu' o meno pronunciate in funzione della coppia scelta. Qui sotto vediamo alcuni esempi di risposte al variare di r e fi.

time resp.

Ora pero' c'e' una domanda che tutti quanti si stanno facendo:"Non c'e' un modo piu' furbo che provarle tutte per stabilire quanto durera' il movimento?".

Purtroppo la risposta non e' sempre si, ho trovato una relazione tra la posizione dei poli e la durata della risposta, ma questa vale solo nel caso di poli reali coincidenti. Il caso di poli complessi coniugati e' piu' complesso (scusate il gioco di parole) e temo vada analizzato con il metodo: di volta in volta.

Per quanto riguarda il caso di poli reali coincidenti: ho provato diverse posizioni per i poli, tracciando ogni volta l'evoluzione completa del movimento e considerando questo concluso al superamento del 98% del gradino in ingresso. Ho annotato le durate corrispondenti trovando cosi' i punti rossi segnati nel grafico qui sotto.

points fitting

Poi ho cercato la curva che meglio approssima i punti individuati, dopo un po' di tentativi ho individuato la curva in verde, come fitting non sara' perfetto ma a me sembra piu' che sufficiente per i nostri scopi. La curva ha questa espressione:

curve equation

Come primo risultato, almeno per me, e' davvero incoraggiante, purtroppo questa formula serve a poco, molto piu' interessante sarebbe invece la forma inversa, che permetterebbe di calcolare la posizione dei poli data la durata desiderata del movimento. La forma inversa dovrebbe essere questa (se non ho sbagliato i conti):

inverse equation

La verifica sembra darmi ragione, forse questa volta ho indovinato!

points fitting

Un esempio concreto

Questo e' un esempio di utilizzo di Fluid per animare degli sprite: il movimento dell'anello principale e' ottenuto tramite Fluid partendo da un segnale a gradini, il movimento del secondo anello (quello azzurro) e' ottenuto tramite un altro filtro Fluid a partire dalla posizione del primo anello. Il movimento non sembra naturale?

Qui sotto un grafico ottenuto dalla demo stessa: mostra il segnale a gradini e le posizioni assunte nel tempo dai due anelli.

Fluid output example

Download

fluid-0.02.tar.gz Pacchetto completo di fluid, compresi tutti gli script per generare la documentazione (compresi i grafici di questa pagina). Gli script richiedono LaTeX e l2p, non che il famosissimo gnuplot. online documentation

Questo sito e' stato realizzato interamente con vim.
Grazie a tutta la comunita' open source, alla free software foundation e chiunque scriva software libero.