La marionetta , astrazione I°

Il prossimo progetto è definire una Picture parametrica che rappresenti una marionetta con le posizioni regolabili dai parametri.
I parametri dovranno infine essere riportati alle altezze dei fili che sostengono la marionetta.
Svilupperemo un modello di marionetta in 2d.
Ecco il risultato dove facciamo ruotare il corpo e basta.
marionetta gira solo corpo
Ed ecco il risultato di una interpolazione tra varie configurazioni della marionetta.
marionetta interpolante

Astrazione

Una figura rigida in 2 dimensioni è completamente piazzata da un punto e una direzione con verso. Il punto fissa uno dei suoi punti che lo consideriamo centro di rotazione. La direzione con verso fornisce l’angolo di rotazione intorno a quel punto.

data Punto = Punto (Float,Float) deriving (Eq,Show)

Definiamo un nuovo dato che rappresenta i punti nel piano. Potevamo accontentarci di un sinomimo di (Float,Float). La dichiarazione finisce con una clausola “deriving” con argomento la classe Show ed Eq. Questo significa che chiediamo al compilatore di costruire per noi le istanze di quelle classi per il datatype Punto.
Le istanze della classe Show dichiarano una funzione show che trasforma i loro valori in String, per permetterci di stamparli.
Le istanze della classe Eq dichiarano una funzione (==) , quindi un operatore, che ci permette di confrontare due valori per l’uguaglianza. Proviamole.

*Main> let p = Punto (4,6)
*Main> show p
"Punto (4.0,6.0)"
*Main> p == p
True
*Main> p == Punto (4,5)
False

Ed ora vediamo i tipi di show e (==)

*Main> :t show
show :: Show a => a -> String
*Main> :t (==)
(==) :: Eq a => a -> a -> Bool

La firma di show si legge : “se il tipo ‘a’ è istanza di Show allora show va da ‘a’ a Sring”
La firma di (==) si legge : “se il tipo ‘a’ è istanza di Eq allora (==) va da ‘a’ a ‘a’ a Bool”

Per la nostra astrazione useremo molto la somma di punti, per definire gli spostamenti.
Il compilatore non può inventarsi le istanze di Num , classe che introduce l’operatore (+) nelle sue istanze, e quindi la scriviamo. La classe Num introduce molto di più della somma, ma a noi non serve altro e quindi definiamo queste funzioni come errori. La funzione error fa abortire il programma.
La funzione negate serve a costruire la differenza che ci tornerà utile ed ha una definizione ovvia, l’inverso della ascissa e dell’ordinata.

instance Num Punto where
	Punto (x,y) + Punto (x1,y1) = Punto (x+x1,y+y1)
	negate (Punto (x,y)) = Punto (negate x,negate y)
	(*) = error "Punto Num method undefined used"
	abs = error "Punto Num method undefined used"
	signum = error "Punto Num method undefined used"
	fromInteger = error "Punto Num method undefined used"

Proviamo somma e differenza

*Main> let p = Punto (4,6)
*Main> let q = Punto (3,2)
*Main> p + q
Punto (7.0,8.0)
*Main> p + q == q + p
True
*Main> p - q
Punto (1.0,4.0)
*Main> p + (negate q)
Punto (1.0,4.0)
*Main> 

Per gli angoli facciamo un sinonimo per essere più chiari nelle firme.

type Angolo = Float

Una funzione che sicuramente ci tornerà utile ci calcola il punto che si ottiene ruotando un punto intorno ad un altro dato un angolo. Gli argomenti sono in ordine: il centro di rotazione, l’angolo e il punto da ruotare.

ruota :: Punto -> Angolo -> Punto -> Punto
ruota q x p = let
	Punto (a,o)  = p - q  -- calcola il vettore da ruotare e lo smonta in ascissa e ordinata
	z = x*pi/180 + atan2 o a -- aggiunge l'angolo in argomento all'angolo del vettore
	r = sqrt (a ^ 2 + o ^ 2) -- calcola la lunghezza del vettore
        in q + Punto (r * cos z, r * sin z) -- calcola il vettore ruotato e lo sposta nel centro di rotazione

Al di là della semplice matematica, con l’utilizzo della funzione atan2 fornita dalla libreria di base, dobbiamo notare la presenza del costruttore Punto in due posti.
Nel secondo esso costruisce il nuovo punto.
Nel primo esso viene utilizzato per smontare il valore ottenuto da “p-q”, per conoscerne le coordinate: questa tecnica si chiama “pattern matching” e chiude il cerchio dei datatype, permettendoci appunto di smontarli. La forza del pattern matching ci risulterà più chiara quando affronteremo dei datatype più complessi.

Ora definiamo una semiretta.

data Semiretta = Semiretta Punto Angolo deriving (Show,Eq)

Una semiretta contiene un punto ed un angolo. Possiamo ora definire una marionetta come un insieme di semirette , ognuna rappresentante un pezzo.
Possiamo anche dire che due insiemi di semirette sono la stessa marionetta in configurazioni diverse se riscontriamo nei due insiemi alcune caratteristiche, che non sono semplici da dire.
Potremmo analizzare le relazioni tra i pezzi e imporre delle regole sulle distanze tra i punti delle semirette, ma invece seguiamo una strada costruttiva.
Intanto osserviamo che i perni che legano i pezzi formano un grafo aciclico. Per fortuna, perché i datatype ricorsivi si prestano a descrivere questo tipo di grafi.
Se osserviamo le marionette delle immagini iniziali e partiamo da uno qualsiasi dei loro pezzi, ci accorgiamo che possiamo sempre raggiungere tutti gli altri pezzi senza dover passare da ogni giunto più di una volta. Questa si chiama aciclicità.
Osserviamo che un grafo aciclico si può sempre trasformare in un albero, “tirando in su” uno qualsiasi dei nodi e lasciando appesi gli altri. Questa forma di dati, ad albero, si descrive facilmente in haskell.
Sappiamo che in ogni nodo ci metteremo una semiretta ma vogliamo essere più generali e pensare ad un albero che contenga valori di un tipo qualsiasi. L’albero che ci serve deve avere più sottonodi per ogni nodo. Per esempio al corpo della marionetta sono attaccate testa, braccia e gambe, 5 sottonodi. Questo tipo di albero si chiama rose-tree e si definisce così:

data Albero a = Albero a [Albero a] deriving (Show)

Si legge : “I valori di tipo ‘Albero a’ sono costruiti dal costruttore Albero e contengono un valore di tipo ‘a’ ed una lista di valori di tipo ‘Albero a’”.
Il valore di tipo ‘a’ è per esempio una semiretta , mentre la lista sono i sottonodi.
Ogni sottonodo è a sua volta un valore di tipo ‘Albero a’ e il gioco si ripete.
Definiamone uno di numeri.

let a1 = Albero 34 [Albero 12 [],Albero 31 []]

Il nodo in cima è il 34 ed ha due sottonodi 12 e 31, entrambi non hanno sottonodi.
Uno di nomi

let a1 = Albero "mimmo" [Albero "carlo" [Albero "francesco" [], Albero "giacomo" []],Albero "piero" []]

Questo ha 3 livelli ma è asimmetrico: il ramo piero finisce lì, mentre carlo ha due sottonodi.

.
Evidentemente per apprezzare un albero , bisogna dare un significato alla struttura. Nel caso della marionetta il significato è che nei sottonodi troviamo le semirette dei pezzi vincolati al pezzo descritto dalla semiretta nel nodo.


testa 		= Semiretta (Punto (0 ,60 )) 90 
corpo		= Semiretta (Punto (0 ,50 )) 270 
bracciodx 	= Semiretta (Punto (15 ,45 )) 300 
avambracciodx 	= Semiretta (Punto (45 ,-5)) (-35) 
cosciadx 	= Semiretta (Punto (10 , -30)) 300 
gambadx 	= Semiretta (Punto (40 , -85)) 270 

simmetrico (Semiretta (Punto (x,y)) t) = Semiretta (Punto (-x,y)) t

marionetta :: Albero Semiretta
marionetta = Albero corpo 
	[	Albero testa []
	,	Albero bracciodx [Albero avambracciodx []]
	,	Albero (simmetrico bracciodx) [Albero (simmetrico avambracciodx) []]
	,	Albero cosciadx [Albero gambadx []]
	,	Albero (simmetrico cosciadx) [Albero (simmetrico gambadx) []]
	]

Annunci

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione / Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione / Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione / Modifica )

Google+ photo

Stai commentando usando il tuo account Google+. Chiudi sessione / Modifica )

Connessione a %s...