processing guida introduttiva alla programmazione visuale processing guida introduttiva alla programmazione visuale alberto cecchi 1 valerio belloni ... processing guida introduttiva alla programmazione visuale indice: prima parte: seconda parte: terza parte: quarta parte: quinta parte: This work is licensed under the Creative Commons AttributionNoncommercial-Share Alike 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-ncsa/3.0/ or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA. 2 p.3 p.34 p.48 p.54 p.60 processing guida introduttiva alla programmazione visuale Processing Programmare per l’arte Prima Parte Possiamo dire che Processing è un software grafico che si occupa di Immagini, Forme, Movimenti, Suoni e Interazioni. Processing è allo stesso tempo un linguaggio di programmazione, un ambiente di sviluppo nonché un metodo di insegnamento della programmazione. Processing è stato inventato da Casey Reas e Ben Fry al fine di insegnare agli studenti nel settore dell’arte e della grafica la programmazione software. Oggi Processing è utilizzato da studenti, artisti, designers e ricercatori per imparare, sperimentare e produrre. Processing rappresenta in pieno il proverbio di Confucio: “se ascolto dimentico, se vedo ricordo, se faccio capisco” Processing, in quanto linguaggio di programmazione, è un linguaggio testuale (si scrivono le istruzioni) pensato per produrre e modificare immagini. Processing è il giusto compromesso tra semplicità (nell’uso) e complessita dei risultati che si possono raggiungere. Sono sufficienti pochi minuti di corso affinchè uno studente possa scrivere le prime righe di codice ed osservare dei risultati. Utenti avanzati possono invece realizzare progetti anche molto complessi scrivendo direttamente delle librerie che possono anche migliorare ed aumentare le funzionalità del software stesso. I lavori fatti in Processing possono essere facilmente esportati nel web e questo aspetto ha facilitato la distribuzione del software e della sua conoscenza. Il sito ufficiale di Processing è www.processing.org, all’interno del sito è presente anche un forum con migliaia di iscritti che discutono assiduamente riguardo le problematiche tecniche e creative. Processing facilita l’insegnamento della grafica digitale, delle tecniche di interazione come ad esempio disegno vettoriale e raster, image 3 processing guida introduttiva alla programmazione visuale processing, gestione degli eventi del mouse e della tastiera e comunicazione nelle reti di computer. Le librerie estendono le funzionalità di base anche alla generazione dei suoni, alla comunicazione dei dati (input/output) alle funzionalità tridimensionali ed esportazioni file in vari formati. Il linguaggio Processing non è molto diverso da tanti altri linguaggi di programmazione. Processing è stato progettato raffinando e rivedendo questi linguaggi che lo hanno preceduto. Questo aspetto è molto importante, infatti possiamo vedere Processing come uno strumento per avvicinarsi alla programmazione. Chi impara a programmare in Processing avrà il futuro molto facilitato qualora volesse imparare a programmare anche in altri linguaggi. L’aspetto principale che differenzia Processing da altri linguaggi di programmazione riguarda la sua versatilità per tutto ciò che è legato alla grafica, al disegno, all’animazione e alle interazioni live. Installare e Aprire Processing Processing è appunto Open Source e completamente gratuito. Per Scaricarlo è sufficiente andare all’indirizzo www.processing.org/ download dove si trovano le versioni per Linux, OSX e Windows. L’ambiente di sviluppo di Processing (Processing Development Environment PDE) è composto da un semplice editor testuale dove vengono scritte le istruzioni. PDE è anche l’estensione dei file di lavoro prodotti in Processing. Poi troviamo un’area messaggi, una console testuale e una toolbar dalla quale possono essere effettuate le operazioni più comuni (nuovo, apri, salva, esporta…). Sempre nella Toolbar in alto sono presenti i due tipici pulsanti degli ambienti di programmazione (Stop e Run). Ecco la lista completa dei pulsanti presenti nella Toolbar: RUN STOP NEW OPEN SAVE EXPORT 4 processing guida introduttiva alla programmazione visuale Quando si lancia un programma (Run) il risultato compare in una finestra che si apre automaticamente (display window). Ogni programma scritto con Processing (attraverso l’editor testuale) si chiama Sketch. All’interno dell’editor testuale sono presenti le due tipiche funzionalità Taglia e Incolla (Cut and Paste) e Cerca e Sostituisci (Search and Replace). L’area messaggi, in basso ci dice ad esempio se una volta lanciato il programma ci sono errori. In pratica l’area messaggi è il mezzo che Processing usa per comunicare con noi, scrivendoci in quell’area qualunque tipo di messaggio riguardo il suo comportamento (ad esempio se si scrive uno Sketch errato sarà l’area messaggi ad avvertirci). L’area della conosole testuale (sotto l’area messaggi) ci scrive dei messaggi in forma più estesa e dettagliata della message window). Questa area ha anche la funzione di controllo durante le operazioni di programmazione. Ad esempio quando si vuole controllare una fare intermedia della programmazione, possiamo far comparire in questa finestra i dati desiderati (più avanti sarà tutto più chiaro). I menu di Processing sono solo 6: Processing File Edit Sketch Tools Help Questa breve lista dei menù all’interno dei quali troviamo pochi comandi, rende l’idea della semplicità dell’ambiente di sviluppo Processing. Vedremo i comandi all’interno dei menù più avanti. Quando si crea un nuovo Sketch (programma) Processing crea automaticamente anche una specifica cartella che avrà lo stesso nome dello sketch (come spiegato prima il file dove è scritto il programma ha l’estensione .pde). Processing è un ambiente in grado di gestire file multimediali pertanto se all’interno del nostro progetto per esempio aggiungiamo un suono o un’immagine questi file andranno a collocarsi automaticamente dentro la cartella data, all’interno della cartella dello Sketch. Processing permette di lavorare secondo la logica “drag and drop” pertanto per aggiungere un’immagine al nostro programma è sufficiente prendere il file e trascinarlo sopra l’editor testuale dove stiamo scrivendo il nostro Sketch. 5 processing guida introduttiva alla programmazione visuale La cartella dello Sketch che a sua volta conterrà anche la cartella data con tutti i file multimediali aggiunti si troverà in una directory del computer che possiamo decidere dal menù File/Preferences. Programmare in Processing Abbiamo detto che programmare in Processing è molto semplice pertanto il modo migliore per capire questo concetto è iniziare a scrivere qualche semplice programma cercando di comprenderne il senso. //Creiamo una finestra 320 x 240 pixel// size(320, 240); background(255); Abbiamo scritto tre righe nella finestra testuale dell’ambiente di sviluppo di Processing. La prima riga potrebbe sembrare semplice e tutti i programmatori giovani o inesperti o egoisti o masochisti tendono a trascurare questo tipo di istruzione che invece è determinante. Ogni volta che scriviamo un programma dobbiamo aggiungere dei commenti che possano essere letti dai programmatori ma che non vengano interpretati come istruzioni dal programma stesso. I commenti sono importanti perchè attraverso i commenti il programmatore dichiara le operazioni che compie in modo che potrà rileggerli in futuro oppure in modo che altri programmatori che apriranno il programma leggendo i commenti possano capire come ha ragionato il programmatore precedente. Commentare un programma significa comunicare con se stessi (nel futuro) e con gli altri. Non commentare un programma significa non comunicare ne con se stessi (quando un anno dopo aprirete un programma fatto e non commentato non ricorderete quali ragionaventi avrete fatto e vi dispererete) ne con altre persone alle quali per lavoro o per divertimento volete lasciare in eredità il vostro lavoro. Un programma non commentato è spazzatura. Questo aspetto così centrale della programmazione risulta ancor più centrale quando ci riferiamo ad un programma Open Source come Processing. Processing non costa nulla perchè delle persone buone d’animo hanno lavorato a lungo e hanno deciso di larsciarci in eredità gratuita il loro lavoro. Un programmatore che non scrive i commenti sui programmi realizzati in processing contravviene alla 6 processing guida introduttiva alla programmazione visuale filosofia Open Source perchè i suoi programmi saranno difficilmente interpretabili da altri programmatori. La stesso giudizio però vale anche per i programmatori che lavorano in ambiti commerciali. Un programmatore che non commenta i propri software danneggia economicamente il suo datore di lavoro e tutto l’ambiente nel quale lavora. Torniamo quindi alla nostra prima riga di programma scritta in Processing //Creiamo una finestra 320 x 240 pixel// I due slash “//” indicano esattamente l’inizio e la fine di un commento del programmatore. Questo commento è molto semplice agli occhi di un programmatore esperto potrebbe sempbrare inutile ma in realtà quanto più diffusamnte è documentato un software tanto più facile sarà la sua lettura e correzione in futuro. Per scrivere dei commenti che siano più lunghi di una riga Processing ci permette di utilizzare anche la seguente forma: /* I commenti sono molto importanti possiamo scriverli anche in più righe il software non interpreta queste istruzioni */ Vediamo invece ora la prima istruzione (o funzione) vera e propria: size(320, 240); Possiamo notare in questa istruzione tre elementi importanti: - Il comando “size” - I parametri (320, 240) - Il simbolo che chiude l’istruzione “;” Il comando “size” stabilisce che deve essere creata una finestra. Questa è la tipica istruzione con cui si inizia a programmare in Processing. Con “size” si stabiliscono le dimensioni della finestra sulla quale poi compariranno i risultati del nostro programma. Le dimensioni che abbiamo stabilito in questo caso sono 320 pixel in larghezza (asse X) e 240 pixel in altezza (asse Y). Con “;” invece diciamo a Processing è terminata l’istruzione. Da notare la seguente istruzione: 7 processing guida introduttiva alla programmazione visuale //size(320, 240); Poichè la riga inizia con “//” Processing interpreterà tutto ciò che segue come un commento e quindi quando si lancerà il programma non verrà effettuata nessuna azione. A volte i programmatori disordinati tendono a commentare molto le istruzioni durante il processo produttivo. Questa operazione se non documentata è un operazione che genera solo confusione. Pertanto quando si commenta un’istruzione è bene spiegare, attraverso un ulteriore commento il motivo per il quale sono presenti delle istruzioni commentate. Rispetto ai paramentri è importante notare che ad esempio nel caso di “size” debbono essere 2 e la loro posizione è obbligatoria. Al primo posto si mette il valore delle X e al secondo posto (dopo la virgola) si mette il valore delle Y. Infine da notare un aspetto molto impor tante della programmazione, la convenzione di chiudere l’istruzione con “;” potrebbe sembrare inutile e noiosa. Potrebbe anche essere così, ma comunque è dobbiamo capire che qualora non mettessimo quel dettaglio “;” il nostro programma non funzionerà. I linguaggi di programmazione usano una sintassi molto più rigida della sintassi che si usa quando si parla o quando si scrive una mail. Quando scriviamo una mail ad un amico e ci dimentichiamo una virgola nel contenuto del nostro messaggio esso arriverà a destinazione uguamente. Se invece facciamo un errore quando scriviamo l’indirizzo mail il messaggio non arriverà mai al destinatario. Scrivere un’istruzione in Processing è come scrivere un indirizzo mail, se non metti la chiocciola o sbagli anche solo un puntino non funzionerà nulla. Gli aspetti formali della programmazione sono importanti e non possono essere tralasciati. Un altro aspetto formale che non possiamo tralasciare in Processing è che i comandi sono “case sensitive”. “Size” e “size”non sono la stessa cosa, il comando “size” deve essere scritto con tutte le lettere minuscole, se non lo facciamo Processing non interpreta il comando. “Size” per Processing non significa nulla. Torniamo ora alla terza riga del nostro breve programma: background(255); 8 processing guida introduttiva alla programmazione visuale Possiamo facilmente distiguere il comando “background” il parametro tra parentesi e il termine della riga di istruzione “;”. Il comando specifico “background” indica a Processing il colore che dovrà avere lo sfondo della finestra che abbiamo appena creato. 255 indica il colore bianco. Una volta terminato di scrivere il nostro programma in tre righe lo potremo lanciare cliccando sul pulsante RUN. Potremo interrompere l’esecuzione del nostro programma cliccando sul pulsante STOP. Numeri e Variabili Abbiamo creato il nostro primo breve programma in Processing. Abbiamo compreso alcuni principi base della programmazione. Questi principi sono pressochè universali nella programmazione e comprenderli in processing ci permetterà poi di applicarli a praticamente qualunque altro linguaggio di programmazione. Nel nostro primo programma abbiamo visto che i comandi possono avere dei parametri e che questi parametri si esprimono in numeri. Nella programmazione è normale però esprimere i numeri anche attraverso delle variabili. Queste due espressioni producono lo stesso risultato: //senza variabili background(0); //con variabili int X = 0; background (X); Nel secondo caso abbiamo fatto due operazioni. Con la prima operazione abbiamo detto al programma di attivare l’uso di una variabile che abbiamo chiamato “X” e abbiamo assegnato un valore a questa variabile “0”. Il risultato è che “background” avrà un valore pari a zero, proprio come nel primo esempio. Le variabili sono molto utili nella programmazione proprio perchè sono “variabili” e quindi il loro valore può cambiare in base a degli 9 processing guida introduttiva alla programmazione visuale eventi , oppure possono cambiare in base a delle operazioni matematiche. Le variabili sono molto utili anche perchè memorizzano dei valori e permettono di riutilizzarli quando serviranno in futuro. Esistono diversi tipi di variabili; nel nostro esempio “int” indica una variabile che può assumere un valore numerico intero (senza valori dopo la virgola). “float” è un altro tipo di variabile numerica che può invece contenere anche valori dopo la “virgola”. Importante: come in quasi tutti i linguaggi legati all’informatica la virgola che noi utilizziamo nei numeri per indicare i valori decimali, deve essere sostituita dal punto “.”. Nei linguaggi di programmazione 10.5 equivale al 10,5 nella notazione che utilizziamo in Italia. Da notare che non è possibile fare operazioni matematiche mescolando due variabili di tipo diverso (una “int” e l’altra “float”). Nel linguaggio comune della programmazione l’istruzione: int X = 0; viene detta inizializzazione o dichiarazione di una variabile. Questo tipo di istruzioni generalmente vengono messe all’inizio dei programmi e comunque debbono essere posizionate sempre prima che si utilizzi la variabile stessa. La seguente sequenza non funzionerebbe: background (X); int X = 10; Se vogliamo utilizzare la variabile “X” prima la dobbiamo inizializzare “int X = 10;”. Una volta che la variabile è stata inizializzata è possibile utilizzarla tutte le volte che si desidera. Inoltre dopo l’inizializzazione la variabile può essere “manipolata”. Nell’esempio seguente osserviamo che: int X = 10; X = 6; background (X); la variabile “X” è stata prima inizializzata poi successivamente è stato assegnato un nuovo valore alla variabile “X = 6;”. 10 processing guida introduttiva alla programmazione visuale Pertanto il valore finale di “background (X);” sarà 6. Ovviamente per brevità sarebbe stato più intelligente scrivere: int X = 6; background (X); il risultato sarebbe stato identico. Il nome delle variabili Abbiamo visto quindi che le variabili hanno un nome. Ho utilizzato “x” e come primo passagio potrebbe essere anche corretto ma quandi si scrive un programma più complesso è invece consigliabile dare un nome “sensato” alle variabili. Il lungo discorso che ho fatto nelle pagine precedenti riguardo i commenti vale anche per le variabili. Ipotizziamo ad esempio di voler disegnare un cerchio attraverso un programma e ipotizziamo che questo cerchio cambierà di dimensione durante l’esecuizone del programma stesso. Potremmo assegnare a questa variabile semplicemente una lettera “X” oppure “C”, ma se immaginiamo che il nostro programma sarà abbastanza complesso allora sarà sicuramente meglio utilizzare un nome della variabile più esteso. Possiamo chiamare la variabile che gestirà il nostro cerchio ad esempio “cerchio”. In questo modo quando riapriremo il programma in futuro oppure qualcun’altro aprirà il nostro programma il nome della variabile permetterà facilmente di comprendere di che variabile si tratta e per che cosa viene utilizzata. Pertanto quando dichiarerò la mia variabile all’inizio del programma scriverò int cerchio = 10; anzichè int X = 10; Il termine cerchio ci aiuterà molto in futuro quando il nostro programma andrà progressivamente coplicandosi. Ma in realtà un buon programmatore dovrebbe precedere la dichiarazione della variabile con un commento che spiega a cosa serve quella variabile. Ad esempio: 11 processing guida introduttiva alla programmazione visuale // ecco la variabile che gestisce // il raggio del cerchio int cerchio = 10; Il nome delle variabili è sempre meglio che sia “parlante”. Alcuni nomi però sono inibiti e non possono essere utilizzati per le variabili. Ad esempio non posso nominare una variabile “int” perchè facendo questo complicherei molto la vita a Processing che potrebbe fare confusione. Infatti “int” è anche un termine assegnato ad un comando. Di seguito una lista dei nomi di variabili che non possiamo usare in Processing: abstract assert boolean break byte case catch char class const continue default do double else enum extends false final finally float for goto if implements import init instanceof int interface long native new null package private protected public return setup short start static 12 processing guida introduttiva alla programmazione visuale stop strictfp super switch synchronizedthis throw throws transient true try update void volatile while Questo aspetto è comune a praticamente tutti i linguaggi di programmazione. Un consiglio che posso dare ai programmatori italiani è proprio quello di dare un nome in italiano alle variabili. Poichè i comandi tipicamente sono in inglese si eviterà facilmente di utilizzare un nome vietato. Esiste poi una lista di variabili che gestisce autonomamente Processing. Quando ad esempio abbiamo generato la finestra: size(320, 240); Processing ha automaticamente immagazzinato in un due variabili “width” e “height” questi valori. Se in futuro vorremo utilizzare questi valori potremo utilizzarli richiamando direttamente le variabili. Variabili e calcoli matematici Le variabili normalmente vengono utilizzate anche per effettuare calcoli matematici. Le operazioni di base che si effettuano sono più “+”, meno ”-“, per ”*” e diviso “/”. Normalmente il risultato di un’operazione matematica viene memorizzato nelle variabili. Ad esempio: int x = 2 + 3; trasferisce il risultato della somma “2+3” nella variabile X al momento della sua inizializzazione “int”. Oppure: int x = 4; 13 processing int int int int y s d m guida introduttiva alla programmazione visuale = = = = 6; x + y; s / 2; (x * y)-2; dichiara il valore delle variabili “x” e “y”, poi viene sommato e il risultato è memorizzato nella varibile “s” (da cui avremo s = 10). In “d” invece memorizziamo il risultato di della somma precedente dividendola per 2 (da cui otterremo d = 5). Infine nella quinta riga abbiamo una vera e propria espressione. Trasferiamo nella variabile “m” il risultato della moltiplicazione di “x” per “y” e successivamente, fuori dalle parentesi, sottraiamo 2 (da cui otteniamo m = 22). Da notare che il risultato di un calcolo matematico deve sempre essere associato a una variabile. Infatti se nella terza riga scriviamo solo: x + y; otterremo un errore quando manderemo in RUN il nostro sketch. L’uso delle parentesi cosi come nella matematica serve per dare la priorità ai calcoli. Inoltre moltiplicazione e sottrazione hanno la priorità su addizione e sottrazione. Le variabili posso essere utilizzate molte volte all’interno della stessa espressione. A volte siamo costretti a compiere diverse operazioni ma non ci interessato i risultati intermedi bensì solo il risultato finale. Nel seguente programma: int int int s = x y s s = = = / 4; 6; x + y; 2; dopo avere calcolato “x+y” e memorizzato il risultato in “s” utilizziamo nuovamente “s” nella riga successiva per memorizzare il valore “s” della riga precedente diviso per 2. Operando in questo modo abbiamo “risparmiato” una variabile però abbiamo perso il risultato temporaneo della somma “x + y”. Quando Processing 14 processing guida introduttiva alla programmazione visuale eseguirà la quarta riga trasferirà in “s” il risultato della nuova operazione (“s/2”) e il precedente valore di “s” sarà perso. Iniziare a disegnare Le operazioni matematiche che si possono effettuare in Processing sono molte di più delle quattro operazioni di base, ma stiamo procedendo secondo un livello di difficoltà progressivo. Abbiamo capito come si scrive un’istruzione in Processing e abbiamo capito l’importanza della sintassi. Inoltre abbiamo introdotto il concetto di variabile nella programmazione. Questi primi elementi ci permettono di iniziare a scrivere un programma leggermente più complesso. Per fare questo però ci avvarremo dell’aspetto maggiormente potente in Processing, cioè le funnzionalità grafiche. Credo che comprendere una formula matematica sia molto più semplice se il risultato di questa formula viene tradotto in una dimensione grafica. Prima quindi di addentrarci nei meandri della programmazione e dell’uso intensivo della matematica iniziamo quindi ad osservare le funzionalità grafiche di Processing. La conoscenza delle funzionalità grafiche faciliterà molto lo sviluppo di formule matematiche più complesse. Abbiamo iniziato la nostra programmazione scrivendo tre righe: //Creiamo una finestra 320 x 240 pixel// size(320, 240); background(255); che, abbiamo visto cliccando su RUN, generano una finestra di sfondo bianco della dimensione di 320 pixel di larghezza e 240 pixel di altezza. Per disegnare qualche cosa dentro questa finestra è necessario prima di tutto comprendere il concetto di coordinate. L’asse delle X scorre in orizzontale mentre l’asse delle Y scorre in verticale. L’angolo in alto a sinistra della nostra finestra ha le coordinate (0, 0). L’angolo in basso a destra ha coordinate (320, 240). L’angolo in alto a destra ha coordinate (320, 0) mentre l’angolo in basso a sinistra ha coordinate (0, 240). figura 2 disegno delle coordinate 15 processing guida introduttiva alla programmazione visuale L’istruzione grafica piùsemplice di Processing permette di generare un punto nello schermo. Questa è la sua sintassi: point(X, Y); questo comando grafico ha due parametri, “X” ed “Y” che indicano le coordnate della finestra nella quale verrà disegnato il punto. Se ad esempio decidessimo di disegnare il punto esattamente al centro della finestra che abbiamo appena creato sarà sufficiente aggiungere una quarta riga: //Creiamo una finestra 320 x 240 pixel// size(320, 240); background(255); point(160, 120); Dividendo per due la larghezza e l’altezza della finestra ottengo le coordinate “X,Y” del punto centrale della finestra. Per comprendere meglio il funzionamento del sistema delle coordinate è sufficiente cambiare i valori “X,Y” del comando point. Da notare che questi valori debbono necessariamente rimanere all’interno dell’intervallo della finestra stessa. Per generare più punti nella finestra aggiungo più volte la stessa istruzione cambiando le coordinate. Mantenendo fisso uno dei due valori delle coordinate si ottengono dei punti lungo l’asse verticale o orizzontale a seconda del valore tenuto fisso. size(320, 240); background(255); //Creiamo tre punti sull’asse verticale //tenendo il valore “X” stabile point(160, 60); point(160, 120); point(160, 180); L’aspetto centrale di Processing in quanto strumento didattico è proprio la facilità con la quale possiamo visualizzare dei principi matematici e statistici. Le coordinate X,Y le ritroviamo in infiniti sistemi e rappresentazioni. Manipolare in Processing i valori del 16 processing guida introduttiva alla programmazione visuale comando “point” permette di comprendere immediatamente in modo visivo il funzionamento delle coordinate. Questa guida può essere letta anche come un’introduzione ai principi della matematica, della programmazione e degli algoritmi, possiamo vedere Processing come lo strumento più veloce per comprendere questi principi. Una volta compreso il principio delle coordinate sarà molto semplice con processing disegnare tutte le forme geometriche di base. Ad esempio questa è la sintassi del comando per disegnare una linea: line(x1, y1, x2, y2); Con questo comando viene eseguita una linea che come estremi due punti individuati con “X1,Y1” e “X2,Y2”. size(320, 240); background(255); //Creiamo una linea diagonale //che attraversa la finestra //dall’angolo in alto a sinistra //all’angolo in basso a destra line(0, 0, 320, 240); Per fare una linea in senso orizzontale il parametro Y dei due punti dovrà rimanere costante. Il seguente comando genera una linea che attraversa la finestra orizzontalmente alla sua metà. line(0, 120, 320, 120); Per disegnare diverse linee è sufficiente mettere un’istruzione dopo l’altra nello stesso Sketch. Per disegnare diverse linee con dei punti in comune è sufficiente scrivere più istruzioni dove ogni linea ha due coordinate uguali alla linea successiva. //figura composta da due linee //che hanno un punto in comune //100,120 line(0, 120, 100, 120); 17 processing guida introduttiva alla programmazione visuale line(100, 120, 80, 220); Unendo delle linee possiamo disegnare triangoli, quadrilateri, rettangoli e linee spezzate. //triangolo prodotto //disegnando 3 linee line(0, 120, 100, 120); line(100, 120, 80, 220); line(80,220,0, 120); Ovviamente però Processing prevede le istruzioni per disegnare direttamente Triangoli, Quadrilateri, Rettangoli ed Ellissi. triangle quad rect ellipse Ecco spiegato di seguito il funzionamento di ogni istruzione: triangle (X1, Y1, X2, Y2, X3, Y3); Attraverso i tre punti vengono disegnati i tre lati del triangolo. quad (X1, Y1, X2, Y2, X3, Y3, X4, Y4); Attraverso i quattro punti vengono definiti i quattro lati del quadrilatero. Il quadrilatero che ne deriva può avere anche tutti i lati diversi e non avere lati paralleli. Importante però quando si disegna un quadrilatero è di seguire un ordine dei lati. Se ad esempio scrivete questa istruzione quad (1, 1, 50, 10, 100, 100, 130, 100); succede che anzichè generare un quadrilatero genererete due trinagoli. Ciò succede perchè nel passaggio dal secondo punto al terzo e dal terzo al quarto Processing segue l’ordine dei punti e quindi i lati del quadrilatero si incrocieranno. 18 processing guida introduttiva alla programmazione visuale Scambiando il terzo punto con il quarto otterremo invece un quadrilatero: quad (1, 1, 50, 10, 130, 100, 100, 100); Per quanto riguarda il rettangolo Processing ragiona in modo leggermente diverso, ecco la sintassi: rect(x, y, width, height) si individua con i parametri “X,Y” l’angolo in alto a sinistra del rettangolo e successivamente si dichiara la larghezza e l’altezza del rettangolo. Ovviamente se “width” e “height” hanno lo stesso valore succede che il rettangolo diventa un quadrato. Infine ecco l’istruzione per generare ellissi e cerchi: ellipse (X, Y, width, height); Con “X, Y” si stabilisce il centro dell’ellisse mentre con “width, height” si stabiliscono larghezza e altezza dell’ellisse. Se i due parametri “width, height”sono uguali si ottiene un cerchio perfetto. Abbiamo quindi visto come si disegnano le figure geometriche di base con Processing. Gli esempi descritti sopra fanno sempre riferimento a delle forme “stabili” legate cioè a dei numeri “fissi”. Volendo effettuare dei disegni più complessi e dinamici potremmo associare le nostre forme a delle variabili. Inoltre le forme che abbiamo disegnato fanno sempre riferimento ad un contesto “neutrale”dove il colore di sfondo è sempre bianco e le figure sono bianche bordate di nero. Ad esempio il seguente Sketch size(320, 240); background(255); //Creiamo un quadrilatero quad (1, 1, 50, 10, 130, 100, 100, 100); //mettiamo sopra al quadrilatero un cerchio ellipse (20,20, 50, 50); 19 processing guida introduttiva alla programmazione visuale produrrà graficamente il quadrilatero che conosciamo e successivamente sopra di esso un cerchio le cui coordinate del centro sono “20, 20” ed il cui raggio è 50. Il cerchio come possiamo vedere ha uno sfondo bianco pertano si sovrappone, coprendolo in parte, al quadrilatero. Questo comportamento ci spiega due aspetti di Processing. Prima di tutto dobbiamo capire che Processing come tutti i linguaggi di programmazione esegue i comandi leggendoli in sequenza. La sequenza di esecuzione diventa sempre più importante e strategica quando si complica lo Sketch. Inoltre le figure grafiche hanno un riempimento e non sono trasparenti. Per vedere meglio questo riempimento possiamo cambiare il colore della finestra. background(0); Canbiando il valore di background da 255 (bianco) a 0 (nero) vediamo ancora maglio che le forme geometriche create hanno un colore di riempimento. I numeri compresi tra 0 e 255 rappresentano tutte le gradazioni che vanno dal nero al bianco, cioè sono dei grigi sempre meno intensi che alla fine 255 diventa un bianco. Questa modalità di classificazione dei colori vale anche per il colore di riempimento delle forme. Quando questo colore non viene dichiarato Processing stabilisce autonomamente un colore (questo in informatica viene definito valore di default). Per vedere ad esempio quale è il colore di default delle finestre è sufficiente cancellare l’istruzione background dal nostro sketch. Mandando in run Processing vediamo che la finestra assume un colore di sfondo grigio. I comandi hanno valori di default diversi infatti le forme geometriche che abbiamo creato hanno un valore di default per il riempimento che è il bianco (255). Per cambiare il valore di default per il riempimento delle forme geometriche si utilizza l’istruzione: fill (); questa istruzione deve precedere il comando per la generazione della forma geometrica. Il seguente sketch 20 processing guida introduttiva alla programmazione visuale size(320, 240); fill (80); //Creiamo un quadrilatero quad (1, 1, 50, 10, 130, 100, 100, 100); //mettiamo sopra al quadrilatero un cerchio ellipse (20,20, 50, 50); genera quindi una finestra con un colore di sfondo di default (grigio) e delle forme geometriche con un colore di sfondo stabilito dal valore tra parentesi del comando “fill”. 80 indica un grigio scuro. Il valore fill funziona per tutte le forme geometriche che verranno generate dopo di esso. Possiamo infatti vedere che sia il quadrilatero che il cerchio hanno lo stesso riempimento. Qualora volessimo generare forme geometriche con valori di sfondo diversi dobbiamo semplicemente mettere dei nuovi comandi fill size(320, 240); //Creiamo un quadrilatero fill (80); quad (1, 1, 50, 10, 130, 100, 100, 100); //mettiamo sopra al quadrilatero un cerchio fill (250); ellipse (20,20, 50, 50); Il fill valido è sempre l’ultimo e vale nello sketch finchè non ne dichiariamo uno nuovo. Il cerchio dello sketch qui sopra è pressochè bianco (250) il quadrilatero e grigio scuro (80) e se creassimo una nuoma forma geometrica dopo il cerchio senza specificare un nuovo fill esso sarebbe 250. Le forme grafiche hanno altri valori di default. Ad esempio disegnando le forme geometriche notiamo che il bordo della forma ha un colore e uno spessore. Il cerchio che abbiamo creato nel precedente Sketch ha il bordo nero, questo colore è di default e può essere cambiato mettendo, sempre prima dell’istruzione che genera la forma geometrica , l’istruzione: stroke (255); 21 processing guida introduttiva alla programmazione visuale in questo caso il cerchio avrà un bordo bianco (255) e qualora sia bianco anche il riempimento ovviamente non avremo percezione del bordo stesso. Un modo più corretto per fare scomparire il bordo da una forma geometrica è l’apposito comando nostroke (0); così come possiamo fare scomparire il riempimento con noFill (0); Inoltre possiamo anche decidere lo spessore del bordo delle forme geometriche (e delle linee) attraverso l’istruzione strokeWeight(4); dove il valore tra parentesi indica lo spessore del bordo espresso in pixel. Da notare un aspetto importante: la “W” di Weight è maiuscola e come ho spiegato nelle prime pagine i comandi sono “case sensitive” pertanto dobbiamo scriverli tenendo presenti le maiuscole e le minuscole. In generale su questo aspetto però l’ambiente di sviluppo di Processing ci aiuta poichè se per errore digitiamo un comando errato (es: strokeweight) vediamo che Processing non modifica il colore del comando stesso lasciandolo nero. Quando invece digitiamo un comando che per Processing significa qualche cosa (es: strokeWeight) succede che immediatamente il testo viene cambiato di colore e diventa marrone. Da notare infine che in molti linguaggi di programmazione si segue questa convenzione, la prima parola di un comando si scrive in minuscolo la seconda parola (attacata) inizia sempre con una maiuscola, lo stesso vale per l’eventuali terza o quarta parola. Diversi altri parametri permettono di intervenire sulle forme geometriche ma per ora abbiamo visto quelli essenziali e possiamo procedere oltre nel nostro percorso di addestramento alla programmazione. In seguito scopriremo come gestire i colori (per ora abbiamo visto solamente come gestire i grigi, e come gestire ad esempio le trasparenze nonchè le curve e tante altre cose ...). 22 processing guida introduttiva alla programmazione visuale Ora però è necessario un nuovo salto nella modalità di lavoro. Programmare veramente La programmazione che abbiamo visto fino ad ora è composta da una serie di comandi messi in sequenza. Processing elabora le istruzioni una dopo l’altra partendo dalla prima riga e terminando all’ultima. In pratica Processing legge i programmi che abbiamo fatto fino ad ora come noi leggiamo i contenuti di un libro. Abbiamo visto anche il concetto di variabile. Le variabili sono utili perchè invece di utilizzare sempre i numeri possiamo sostituirli con delle entità il cui stato varia con lo scorrere del programma. I disegni che abbiamo fatto fino ad ora sono molto semplici e sopratutto sono statici. Infatti tutte le istruzioni che abbiamo scritto vengono elaborate sequenzialmente ma ad una velocità tale percui quando premiamo il tasto RUN davanti a noi compare una figura apparentemente statica. Processing è un’applicazione che si presta bene per fare lavori di tipo statico (immagini fisse) ma si presta molto bene anche per fare animazioni. Inoltre anche nell’elaborazione di immagini statiche potrebbe succedere che vogliamo produrre molte volte una figura ad intervalli regolari oppure di dimensioni progressivamene crescenti. Questa tipologia di problematiche: animazioni e ripetizioni (iterazioni) possono essere risolte in modo veloce attraverso delle apposite istruzioni. Partiamo da un caso molto semplice: all’interno della nostra finestra vogliamo disegnare dei punti ad intervalli regolari sull’asse orizzontale, uno ogni 40 pixel. Disegniamo ora questi punti utilizzando il metodo più semplice che abbiamo visto. size(320, 240); background(255); //Creiamo sette punti //sull’asse orizzontale //tenendo il valore “Y” stabile point(40, 120); point(80, 120); point(120, 120); point(160, 120); 23 processing guida introduttiva alla programmazione visuale point(200, 120); point(240, 120); point(280, 120); Abbiamo visto già nelle pagine precedenti tutte le istruzioni che permettono di generare questo nuovo Sketch. Premendo RUN vediamo sette punti a intervalli rogolari che attraversano lo schermo in senso orizzontale. Ora introduciamo l’uso delle variabili. Stabiliamo una variabile che chiamiamo “intervallo” e che ha il valore di 40, successivamente sostituiamo i valori delle coordinate X dei punti con le variabili. size(320, 240); background(255); // dichiaro la variabile int intervallo = 40; //Creiamo sette punti //sull’asse orizzontale //tenendo il valore “Y” stabile point(intervallo, 120); point(intervallo*2, 120); point(intervallo*3, 120); point(intervallo*4, 120); point(intervallo*5, 120); point(intervallo*6, 120); point(intervallo*7, 120); Abbiamo moltiplicato l’intervallo per dei valori progressivamente maggiori ottenendo esattamente lo stesso risultato del precedente Sketch. Il coefficiente di moltiplicazione che applichiamo all’invervallo stabilisce la distribuzione dei punti. Proviamo quindi a trasformare in variabile anche questo coefficiente, chiamandola coefficiente. size(320, 240); background(255); // dichiaro la variabile int intervallo = 40; // dichiaro la variabile // coefficiente int coefficiente = 1; 24 processing guida introduttiva alla programmazione visuale //Creiamo sette punti //sull’asse orizzontale //tenendo il valore “Y” stabile point(intervallo*coefficiente, 120); coefficiente = coefficiente + 1; point(intervallo*coefficiente, 120); coefficiente = coefficiente + 1; point(intervallo*coefficiente, 120); coefficiente = coefficiente + 1; point(intervallo*coefficiente, 120); coefficiente = coefficiente + 1; point(intervallo*coefficiente, 120); coefficiente = coefficiente + 1; point(intervallo*coefficiente, 120); coefficiente = coefficiente + 1; point(intervallo*coefficiente, 120); coefficiente = coefficiente + 1; Leggendo con attenzione lo Sketch appena scritto vediamo che esso è diventato molto più lungo. Infatti per poter incrementare ogni volta il coefficiente è stata aggiunta l’apposita istruzione “coefficiente = coefficiente + 1;”. Comunque un primo vantaggio possiamo coglierlo dal fatto che osservando con attenzione lo sketch vediamo che le istruzioni sono sempre uguali. Infatti le due righe di istruzione: point(intervallo*coefficiente, 120); coefficiente = coefficiente + 1; si ripetono sette volte generando i sette punti. Per velocizzare l’operazione di scrittura dello sketch potremmo scriverlo facendo “copia” e “incolla” delle due righe fino a ripeterle sette volte. La logica della programmazione informatica sta esattamente in questi passaggi: - concepire tutti i passaggi che portano al risultato (abbiamo scritto nel primo Sketch tutte le istruzioni che portavano al risultato desiderato) - concepire questi passaggi in modo che siano ripetitivi (abbiamo scritto nel terzo Sketch istruzioni ripetitive che portano al risultato desiderato) 25 processing guida introduttiva alla programmazione visuale - trasformare i passaggi da ripetitivi a ricorsivi (ora trasformeremo le operazioni ripetitive in ricorsive scrivendo un “ciclo”): size(320, 240); background(255); // dichiaro la variabile int intervallo = 40; // dichiaro la variabile // coefficiente int coefficiente = 1; //Creiamo sette punti //sull’asse orizzontale //tenendo il valore “Y” stabile for (coefficiente = 1; coefficiente coefficiente = coefficiente+1) { point(intervallo*coefficiente, 120); } < 8; L’unico elemento nuovo nello Sketch precedente è esattamente l’istruzione “for” che ha la seguente sintassi: for (coefficiente = 1; coefficiente coefficiente = coefficiente+1) { point(intervallo*coefficiente, 120); } < 8; I principi di ricorsività vengono stabiliti tra parentesi dopo avere scritto l’istruzione “for”. Tra parentesi troviamo tre elementi separati da “;”. Il primo elemento “coefficiente = 1” stabilisce il valore di partenza del ciclo. Il secondo elemento “coefficiente < 8” stabilisce il valore di interruzione del ciclo. Il terzo elemento stabilisce l’aggiornamento del valore che avverrà ricorsivamente (l’incremento della variabile) che nel nostro caso è “coefficiente = coefficiente+1”. In seguito all’istruzione “for” tra le parentesi graffe vengono messe le operazioni da compiere durante tutta la durata del ciclo “point (intervallo*coefficiente, 120)”. 26 processing guida introduttiva alla programmazione visuale Leggiamo come se fosse scritta in lingua italiana l’istruzione “for”del nostro ciclo: Incrementa il coefficiente di una unità e finchè il suo valore è minore di 7 disegna un punto. Attraverso le istruzioni di tipo “for” possiamo quindi ripetere un’operazione finchè non si verifichi una condizione di interruzione. Abbiamo quindi sintetizzato il nostro sketch, da notare che ora, qualora volessimo disegnare 100 punti invece di 7 la lunghezza dello sketch sarebbe sempre la stessa mentre invece nella modalità precendente avremmo dovuto scrivere 100 righe di codice, una per punto. Il seguente Sketch disegna 100 punti, con un intervallo tra un punto e l’altro di 3 pixel: size(320, 240); background(255); // dichiaro la variabile int intervallo = 3; // dichiaro la variabile // coefficiente int coefficiente = 1; //Creiamo sette punti //sull’asse orizzontale //tenendo il valore “Y” stabile for (coefficiente = 1; coefficiente < 101; coefficiente = coefficiente+1) { point(intervallo*coefficiente, 120); } Per fare questa semplice modifica abbiamo solamente modificato la variabile “int intervallo = 3;” ed abbiamo cambiato il valore di interruzione del ciclo “for” con “coefficiente < 100”. La verifica di interruzione del ciclo ”for” avviene attraverso un simbolo comunemente conosciuto: “<”. In processing è possibile anche controllare se un valore è maggiore “>” oppure se un valore è uguale “==”. Nel caso di uguale (come in molti linguaggi di programmazione) si utilizza due volte il simbolo “=” per evitare che il programma compia un’operazione sulla variabile. Pertanto quando scriviamo: 27 processing guida introduttiva alla programmazione visuale X = 5 trasferiamo il valore “5” nella variabile “X” Mentre se scriviamo X == 5 controlliamo se il valore di X è uguale (==) a 5. La programmazione in Processing ci permette quindi di evitare di scrivere infinite operazioni descrivendole una ad una. Attraverso i cicli “for” possiamo ripetere delle operazioni infinite volte cambiando ogni volta l’operazione grazie all’uso delle variabili. Le condizioni Un’altra istruzione (oltre al ciclo “for”) è determinante nel processo di apprendimento della programmazione. Questa istruzione è “if ”. Continuiamo a lavorare nel nostro precedente Sketch e modifichiamolo decidendo che i nostri punti devono diventare delle linee quando il valore del “coefficiente” supera la soglia di 90 (elimino i commenti per risparmiare “carta” visto che sono presenti nello Sketch precedenti). size(320, 240); background(255); int intervallo = 3; int coefficiente = 1; for (coefficiente = 1; coefficiente < 101; coefficiente = coefficiente+1) { if (coefficiente > 90) { line (intervallo*coefficiente, 120, intervallo*coefficiente, 130); } point(intervallo*coefficiente, 120); } Abbiamo aggiunto l’istruzione “if ”: if (coefficiente > 90) { line (intervallo*coefficiente, 120, intervallo*coefficiente, 130); } 28 processing guida introduttiva alla programmazione visuale che controlla se il coefficiente è maggiore di 90 e qualora lo sia disegna una linea verticale lunga 10 pixel (notare il valore della coordinata Y che è prima 120 poi 130). Mandando in RUN lo sketch completo vediamo che Processing disegna 90 punti e poi inizia a disegnare delle linee. La gestione delle condizioni nella programmazione è determinante, infatti ogni volta che verifichiamo se una condizione si verifica automaticamente creiamo un bivio nel percorso possibile che può compiere il programma. Quando al mattino usciamo di casa controlliamo se piove e qualora stia piovendo prendiamo l’ombrello. In quel momento compiamo un’operazione di tipo “if ”. Quando all’interno di un programma ad esempio vogliamo gestire le possibili interazioni con l’utente che lo userà dobbiamo farlo attraverso delle operazioni “if ”. Tutti i software che producono delle interazioni con l’utente lo fanno ragiornando attraverso dei comandi di tipo “if ”. I cicli “for” insieme ai controlli “if ” sono due elementi centrali della logica di programmazione (in processing e praticamente in qualunque altro linguaggio di programmazione). Il controllo “if ” permette anche di gestire la condizione negativa (detta “else” ossia altrimenti). Pertanto possiamo decidere anche di cambiare il nosro sketch così come segue: size(320, 240); background(255); int intervallo = 3; int coefficiente = 1; for (coefficiente = 1; coefficiente < 101; coefficiente = coefficiente+1) { if (coefficiente > 90) { line (intervallo*coefficiente, 120, intervallo*coefficiente, 130); } else { point(intervallo*coefficiente, 120); } } Oltre al controllo precedente (“if coefficiente > 90”) abbiamo aggiunto la verifica della condizione negativa “else”. In questi passaggi è importante seguire anche l’uso delle parentesi poichè se 29 processing guida introduttiva alla programmazione visuale utilizzate in modo errato producono degli errori in fase di esecuzione. Rispetto all’uso delle parentesi vale sempre una regola semplice: le parentesi aperte debbono essere tante quante le parentesi chiuse. Inoltre le parentesi sono sempre legate, quindi se si apre una parentesi tonda si deve chiudere la parentesi tonda. Quando si apre una graffa si deve chiudere la graffa. Inoltre le parentesi possono essere nidificate, messe cioè una dentro l’altra. Nello Sketch sopra ad esempio abbiamo messo dentro alle parentesi del ciclo “for” (che quindi inglobano il resto delle istruzioni) le parentesi relative ad “if ” e ad “else”. Possiamo dire che abbiamo messo due piccole scatole (“if ” e “else”) dentro una scatola più grande (“for”). Programmare le animazioni Le istruzioni che abbiamo visto fino ad ora ci hanno permesso di capire i principi base della programmazione. Per generare delle forme grafiche è sufficiente scrivere le specifiche istruzioni. Le forme grafiche hanno sempre delle coordinate e dei valori (colore, spessore...) che possono essere espressi in numeri o attraverso delle variabili. L’esecuzione delle istruzioni da parte di Processing procede linearmente a meno che, attraverso le istruzioni for non realizziamo dei cicli. Nel caso in cui volessimo però realizzare delle animazioni dobbiamo ragionare in modo diverso. Un’animazione è composta da fotogrammi (immagini fisse) successivi. L’esecuzione dei fotogrammi ad una velocità sufficiente genera un effetto di animazione nel nostro cervello. Processing è un software che ci permette di disegnare attrvaerso le istruzioni grafiche e ci permette anche di disegnare in movimento. Riconfiguriamo ora lo Sketch realizzato precedentemente dandoci un nuovo obiettivo. Produrre attraverso un’animazione dei punti successivi che compaiono progressivamente. Per ottenere questo nuovo effetto dobbiamo utilizzare le istruzioni che permettono di gestire le funzioni: // configuro i parametri // di esecuzione void setup () { 30 processing guida introduttiva alla programmazione visuale size (320,240); background (255); } int intervallo = 0; // definisco la funzione // dentro la funzione // metto le istruzioni ricorsive void draw (){ frameRate (1); point(intervallo, 120); intervallo = intervallo + 10; } Le funzioni utilizzate in questo nuovo Sketch sono due: void setup () {} void draw () {} Con “void setup” vengono definite tutte quelle parti del programma che rimarranno effettive durante la sua esecuzione. Le istruzioni interne alla funzione “void setup” vengono eseguite una sola volta. Infatti dentro le parentesi “{}” sono state messe le due istruzioni tipiche di ogni Sketch per la definizione della finestra e del suo colore di sfondo. Inoltre è stata anche messa l’inizializzazione della variabile “intervallo” che è necessaria un’unica volta. Con “void draw”viene invece definita la funzione ricorsiva che verrà eseguita ricorsivamente senza sosta. “frameRate (1);” stabilisce la velocità dell’animazione che ho impostato ad 1 fotogramma al secondo in modo al fine di cogliere bene gli effetti dello Sketch. Poi metto l’istruzione per disegnare il punto e l’istruzione per spostare di intervalli regolari il punto stesso (intervallo = intervallo + 10). Quando verrà mandato in RUN lo Sketch si otterrà un’animazione alla velocità di un frame al secondo dove compare un nuovo punto ogni frame. Questo punto (grazie al contatore) avrà ad ogni frame una coordinata Y incrementata di 10. Mandando in RUN lo Sketch così come è stato configurato succede che i punti creati nel fotogramma precedente rimangono persistenti nel video pertanto ad ogni fotogramma avremo un punto in più. Qualora invece volessimo avere un effetto di spostamento del punto dovremmo ad ogni nuovo frame cancellare il punto creato nel fotogramma precedente. Per fare questo è sufficiente spostare 31 processing guida introduttiva alla programmazione visuale l’istruzione “background (255);”dalla funzione “setup” alla funzione “draw”. Mandando nuovamente in RUN lo Sketch modificato notiamo che il punto del frame precedente scompare ed abbiamo un effetto di movimento (anche se molto a scatti e lento). Per velocizzare il movimento è sufficiente modificare i parametri frameRate (lo impostiamo a 5 poi a 15). Per dare maggiore fluidità è sufficiente abbassare l’incremento costante della variabile “intervallo”; anzichè 10 mettiamo 3 poi 1. Continuando a modificare questi due parametri possiamo capire sempre meglio il loro uso. Da ricordare che nel cinema e nella televisione si lavora all’incirca ad una frequenza di 25 fotogrammi al secondo. Processing ha un valore di default del “frameRate” di 60. Pertanto se all’interno della funzione “draw” non dichiariamo una velocità questo valore sarà automaticamente impostato a 60 (molto veloce). Da notare infine che, mandando in esecuzione lo Sketch, il punto si sposta progressivamente da sinistra a destra finchè non scompare. In realtà, pur scomparendo il punto Processing continua ad elaborare lo Sketch incrementando il valore “intervallo” anche oltre la dimensione della finestra dichiarata in “size”. Questo aspetto dovrà essere tenuto presente quando verranno programmati degli Sketch sempre più complessi poichè in termini di elaborazione essi impegnano il computer pur non generando alcun effetto visibile. Farsi in casa una funzione I processi base della programmazione sono stati introdotti. Le funzioni sono molto importanti poichè permettono di eseguire in modo ricorsivo delle operazioni. La programmazione in Processing permette anche di definire delle funzioni da richiamare quando se ne ha bisogno. Ad esempio possiamo definire una funzione che disegna due punti che si muovono da sinistra a destra. Costruisco una funzione come segue: void punti () { // con il controllo if // faccio in modo di ripartire // ogni volta che esco dallo schermo if (intervallo > 320) { intervallo = 10; 32 processing guida introduttiva alla programmazione visuale } intervallo = intervallo + 10; point (intervallo, 120); point (intervallo +1, 118); } dopo l’istruzione che introduce le funzioni (void) viede dato un nome alla funzione stessa. In questo caso “punti”. Poi per vedere l’effetto in modo continuativo utilizzo anche una istruzione if che praticamente controlla se i punti hanno superato la dimensione dello schermo “if (intervallo > 320)”. In caso affermativo riporto la variabile “intervallo” al suo valore di partenza (intervallo = 10;) e quindi i punti riappariranno nello schemo a sinistra in modo infinito. Le due istruzioni “point” definiscono la posizione dei due punti che scorreranno in senso orizzontale (infatti come coordinata X hanno una variabile mentre come coordinata Y hanno un numero fisso). L’utilità delle funzioni definite sta nel fatto che possono essere richiamate quando sono necessarie. Pertanto una volta definite potranno essere utilizzate molte volte. Di seguito uno sketch che richiama la funzione “punti” (tolgo i commenti ricorsivi dallo Sketch per brevità). int intervallo = 10; void setup () { size (320,240); } void draw (){ frameRate (15); background (255); punti (); } void punti () { if (intervallo > 320) { intervallo = 10; } intervallo = intervallo + 10; point (intervallo, 120); point (intervallo +1, 118); 33 processing guida introduttiva alla programmazione visuale } Leggendo il contenuto della funzione “void draw ()” vediamo il richiamo alla funzione creata “punti ();”. Per richiamare una funzione creata è sufficiente quindi scrivere il nome della funzione e mettere le due parentesi tonde e la virgola. Il flusso di esecuzione dello Sketch salterà quindi alla funzione “punti” e la scorrerà come se fosse un sottoprogramma. Terminata la funzione Processing tornerà a leggere l’istruzione subito dopo il richiamo. Da ricordare che tutte le istruzioni della funzione “void draw ()” sono eseguite da processing in modo infinito. E’ comunque possibile interrompere questo ciclo infinito attraverso l’istruzione “noLoop ();” sia in un punto specifico durante l’esecuzione della funzione “draw” sia come caratteristica generale dello sketch mettendo l’istruzione nell’area “setup”. Volendo far ripartire il loop infinito di “draw” è invece sufficiente mettere l’istruzione “loop ();” nel punto desiderato. Variabili, funzioni, interazioni (if) e cicli rapresentano la base della programmazione. Chi ha letto e compreso tutto ciò che è scritto nelle pagine precedenti può tranquillamente dire in giro che conosce i rudimenti della programmazione. 34 processing guida introduttiva alla programmazione visuale Seconda Parte I colori Molte istruzioni sono state volontariamente tralasciate nella prima parte del testo perchè l’intento era far comprendere i principi base e successivamente incrementare la conoscenza intorno ad essi. Ad esempio nella Prima parte ho descritto il metodo utilizzato in Processing per definire il bianco, il nero e tutte le tonalità di grigio che stanno in mezzo. Traducendo in termini informatici e matematici la modalità di classificazione utilizzata da Processing per le scale di grigio possiamo dire che vengono utilizzati 8 bit. Infatti 8 bit permettono di ottenere 28 (256) combinazioni diverse (da 0 a 255). Il criterio che processing utilizza per i colori è praticamente lo stesso solamente che applica la scala ai tre colori primari utilizzati per classificare i colori negli schermi (anche quelli televisivi). Questi colori sono Il Rosso il Verde (Green) e il Blu, il metodo viene infatti chiamato RGB. Processing utilizza 8 bit per classificare ognuno dei tre colori primari del metodo RGB. Vediamo prima di tutto un semplice Sketch per capire meglio il metodo: size (320, 240); fill(255, 255, 255); ellipse (50, 50, 20, 20); Lo Sketch produce un cerchio (ellipse) di colore bianco poichè nella istruzione “fill” sono stati impostati i parametri RGB al massimo valore (255, 255, 255). Ne consegue che se impostiamo tutti e tre i parametri al mimimo otteniamo il nero (0, 0, 0). Questo metodo è anche detto additivo. I tre numeri hanno una posizione fissa, il primo indica il Rosso, il secondo il Verde e il tezo il Blu. Di seguito una breve tabella con alcuni esempi di colore ottenuti attraverso il metodo RGB: (255, 0, 0) = Rosso (0, 255, 0) = Verde (0, 0, 255) = Blu 35 processing guida introduttiva alla programmazione visuale (255, 255, 0) = Giallo (150, 150, 150) = Grigio (250, 200, 200) = Rosa Le combinazioni possibili prodotte da 24 bit sono circa 16 milioni di colori. Il metodo RGB può essere applicato in sostituzione del metodo a 8 bit. Ad esempio per tornare allo Sketch precedente potremmo stabilire di voler disegnare un cerchio bianco con bordo rosso. Per fare questo utilizziamo un’istruzione che abbiamo già visto “stroke” però in questo caso dichiariamo i 3 colori primari. size (320, 240); fill(255, 255, 255); stroke (255, 0, 0); ellipse (50, 50, 20, 20); Inoltre aggiungendo l’istruzione background possiamo anche stabilire il colore di sfondo della finestra. Ad esempio se allo sketch precedente aggiungiamo l’istruzione “background (0, 0, 0)” dopo l’istruzione “size”, otteniamo uno sfondo verde. Trasparenza Processing permette anche di gestire un quarto parametro per gestire i colori. Anche questo quarto parametro viene gestito con uno spettro di 8 bit, esso è la trasparenza (anche detto canale alpha). Vediamo uno Sketch: size (320, 240); fill(0, 0, 255, 200); ellipse (50, 50, 20, 20); fill(255, 0, 0, 100); ellipse (40, 40, 20, 20); fill(255, 0, 255, 50); ellipse (20, 20, 50, 50); Il precedente Sketch produce tre cerchi con colori diversi e gradi di riempimento diversi (il quarto parametro dell’istruzione “fill”). 36 processing guida introduttiva alla programmazione visuale Quando il canale Apha è impostato a 0 la figura risulta completamente trasparente mentre quando il canale è impostato a 255 la figura risulta essere piena. Il grado di trasparenza è particolarmente utile quando si hanno delle figure sovrapposte e si vuole lasciare trasparire le figure sottostanti. Eseguendo il precedente Sketch possiamo notare che i tre cerchi sono sovrapposti secondo l’ordine definito nella scrittura dello Sketch: il primo cerchio risulta essere più in fondo e vicino allo sfondo, mentre l’ultimo cerchio risulta essere più in superficie e quindi sopra i due cerchi scritti precedentemente. Il grado di trasparenza è applicabile anche ai grigi. Ecco una tabella esplicativa delle possibili situazioni, riportata all’istruzione “fill” ma estensibile a “background”: fill (100); = riempimento grigio fill (100, 50); = riempimento grigio con un alto livello di trasparenza fill (255, 0, 0); = riempimento con metodo RGB fill (255, 0, 0, 120); = riempimento con metodo RGB e un medio livello di trasparenza Un’ultima cosa interessante sui colori riguarda le variabili. E’ possibile definire delle variabili di tipo colore e successivamente richiamare il colore direttamente scrivendo il nome della variabile. color rosso = color(255, 0, 0); fill(rosso); Ad esempio nelle due righe sopra ho definito il colore rosso e successivamente lo richiamo all’interno di un parametro fill. Questà opportunità permette di gestire colori particolari dei quali magari si è già provata in passato la combinazione. In questo modo mettendo all’inizio dello Sketch il codice del colore sarà più facile in seguito nello Sketch richiamarlo. Nei semplici Sketch questo tipo di funzionalità potrebbero sembrare inutili, ma quando si inizia a scrivere degli Sketch lunghi centinaia di righe ci si accorge dell’utilità di queste funzioni (nonchè dei commenti “//”). Il caso Quando si realizza arte con il computer i colori sono ovviamente determinanti. Designare senza colori è molto più difficile e limitativo. Mentre alcuni tipi di disegni possono essere facilmente 37 processing guida introduttiva alla programmazione visuale riprodotti anche in scale di grigio, altri tipi di rappresentazione necessitano dei colori. Se ben usata, l’opzione dei colori permette di ottenere dei risultati e delle rappresentazioni visive molto più efficaci. C’è un altro aspetto concettuale che riguarda la programmazione che è altrettanto determinante: la casualità (random). Generare dei numeri a caso attraverso il computer permette di ottenere delle forme visuali molto più credibili che non utilizzando delle rigide formule trigonometriche. Le numerazioni casuali sono molto utili ad esempio quando si vuole simulare un movimento di un “soggetto” organico oppure quando si vuole disporre in modo naturale degli oggetti. Ad esempio vuolendo simulare una distesa d’erba potremmo in teoria decidere di mettere un filo ogni 4 pixel ma questa disposizione così “rigida” e costante darebbe immediatamente un effetto estetico di artificialità all’immagine risultante. Se invece disponiamo i fili d’erba in modo casuale entro un certo intervallo (tra 1 e 6 pixel) otteniamo un effetto naturale nella distribuzione dell’erba stessa. Inoltre la casualità è molto importante anche quando si vogliono gestire dei giochi. Tutti gli eventi a partire da un lancio di moneta o di dado possono essere gestiti attraverso la generazione di un evento casuale nel computer. La casualità ha quindi uno spazio importante nella generazione di immagini e nella gestione delle animazioni fatte con Processing e con qualunque altro software. Da notare inoltre che la casualità in quanto tale non è gestibile dai computer. Infatti i computer ragionano in base a regole molto rigide e cicliche e quando gli si chiede di generare un numero casuale essi hanno difficoltà e quindi vanno alla ricerca di eventi da misurare. Il movimento del mouse è un tipico evento misurato dal computer che poi rielaborato più volte produce un numero casuale. Questo aspetto così complesso per il computer è completamente invisibile agli utenti ma è emblematico di quanto sia ritenuto importante il fattore casuale nella programmazione. Nonostante il computer non sia “geneticamente” in grado di produrre eventi casuali, gli si chiede di gestirli perchè attraverso questa casualità si possono simulare infiniti eventi. Modifichiamo quindi l’ultimo Sketch della prima parte utilizzando un fattore casuale: int intervallo = 10; void setup () { 38 processing guida introduttiva alla programmazione visuale size (320,240); } void draw (){ frameRate (5); background (255); punti (); } void punti () { if (intervallo > 320) { intervallo = 10; } intervallo = intervallo + int (random (20)); point (intervallo, 120); point (intervallo +1, 118); } Nella funzione “void punti” ho corretto la linea che calcola il salto che i punti devono compiere ad ogni nuovo frame: intervallo = intervallo + int (random (20)); Precedentemente il valore di incremento era “10” mentre ora il valore di incremento (che definisce lo spostamento dei punti) sarà un valore casuale compreso tra zero e 20 (int (random (20)); ). “random (20); genera un numero casuale con virgola (di tipo “float”) compreso tra 0 e 20. L’istruzione “int ()” serve ad arrotondare i numeri con decimali in numeri interi. Questa istruzione “int” è necessaria in quanto Processing non permette di mescolare numeri con virgola (di tipo float) con numeri interi (di tipo int). Mandando in RUN lo Sketch vediamo che ora i punti si spostano sempre da sinistra a destra ma procedendo in modo irregolare a volte hanno delle accelerazioni mentre altre volte procedono lentamente. Questo andamento è casuale e lanciando più volte lo Sketch si otterranno sempre risultati diversi. Osservando i punti compiere questo tipo di movimenti abbiamo un effetto di movimento che suscita molta più curiosità, la sensazione che dona questo tipo di movimento è più organica e “viva” che non il movimento rettilineo uniforme del precedente Sketch. Per modificare l’effetto degli scatti possiamo agire su diversi parametri. Aumentando il valore di random (20) otteniamo scatti sempre più 39 processing guida introduttiva alla programmazione visuale variegati, mentre otteniamo scatti meno evidenti mettendo dei valori più bassi. Inoltre, aumentando il “framerate” otteniamo maggiore velocità dell’animazione con più fluidità nel movimento dei punti. Interveniamo nuovamente sullo Sketch al fine di aumentare l’effetto organico del movimento: int intervallo = 10; void setup () { size (320,240); } void draw (){ frameRate (10); background (255); punti (); } void punti () { if (intervallo > 320) { intervallo = 10; } intervallo = intervallo + int (random (5)); point (intervallo, int (random (118, 121))); point (intervallo +1, int (random (116, 119))); } Ora, analizzando la funzione “void punti” vediamo che ho inserito un fattore di casualità anche nello spostamento sulla coordinata Y. Pertanto i punti si sposteranno anche leggermente in alto o in basso durante il loro consueto movimento verso destra. Analizzando l’istruzione vediamo che tra parentesi questa volta ci sono due parametri “int (random (116, 119))”. Quando l’istruzione random contiene due parametri significa che è stato definito un parametro inferiore e un parametro superiore dell’intervallo casuale. Nello Sketch precedente, il valore di Y 120 per il primo punto, ora invece sarà un valore casuale compreso tra 118 e 121. 40 processing guida introduttiva alla programmazione visuale Mandando in RUN questo ultimo Sketch vediamo che i due punti spostandosi leggermente anche in alto e in basso avranno un effetto ancor più organico. Sembreranno volare come due mosche. Più avanti affronteremo ancor più approfonditamente questo concetto. Per il momento l’utilizzo della funzione “random” sarà molto utile perchè ci permetterà di osservare degli esempi più complessi e variegati, a volte dai comportamenti inattesi (perchè casuali). Curve Abbiamo già esplorato le forme grafiche di base di Processing (point, line, rect, triangle, quad, ellipse). Osservando i cicli e le funzioni abbiamo capito che possiamo assegnare le coordinate di queste forme geometriche a delle variabili anche casuali. Processing permette anche di generare delle curve secondo un metodo detto di Bezier. In pratica possono essere dichiarate, oltre ai due punti di inizio e fine della curva, anche le tangenti ai due punti. Vediamo un esempio scrivendo il seguente Sketch e mandandolo in RUN: size (320, 240); noFill (); bezier(10, 20, 100, 5, 280, 155, 10, 175); Il punto di inizio e di fine della curva sono la prima e la quarta coordinata dell’istruzione. La seconda e la terza coordinata indicano invece le tangenti. Per capire meglio cosa siano le tangenti consiglio di aggiungere due istruzioni in grado di visualizzarle: size (320, 240); noFill (); bezier(10, 20, 100, 5, 280, 155, 10, 175); line (10,20, 100,5); line (10,175, 280,155); Grazie alle due linee appena disegnate possiamo ora vedere le tangenti. Da notare che la curva risultante dalle nostra istruzioni tende ad essere più accentuata verso il basso. 41 processing guida introduttiva alla programmazione visuale Osservando la tangente più in basso comprendiamo il motivo. La tangente è molto più “lunga” pertanto essa “attrae” molto di più la curva. Semplificando possiamo vedere la curva di Bezier come se fosse un elastico: con la prima e la quarta coordinata vengono fissati (inchiodati) i punti di inizio e fine dell’elastico. Attraverso la seconda e terza coordinata si “attrae” l’elastico. Quanto più lontane sono le coordinate di attrazione tanto più l’elastico sarà teso e quindi la curva sarà più accentuata. Per avere prova della desrcizione appena effettuata scrivete la seguente istruzione: bezier(10, 20, 10, 20, 10, 175, 10, 175); Il punti di tiraggio dell’elastico sono ora gli stessi dei punti di terminazione dell’elastico stesso. Ne risulta che la curva di Bezier è una linea retta (perchè il “tirante” non è attivo avendo le stesse coordinate del punto). Attraverso questo metodo possiamo disegnare praticamente qualunque tipo di cur va. Questa funzionalità risulterà particolarmente utile quando vorremo generare delle forme organiche che molto difficilmente hanno spigoli e angoli netti. Lo Sketch di seguito ci mostra il variare di una curva di bezier al variare delle coordinate delle tangenti: int contatore = 10; void setup () { size (320,240); } void draw (){ frameRate (10); background (255); //incremento il contatore contatore = contatore + 1; // definisco la curva di bezier // associando le tangenti // a un contatore per osservare // il cambiamento della curva bezier (10,10, contatore, 10, 200, 60,200); } 42 contatore, processing guida introduttiva alla programmazione visuale Osservando infine il risultato delle nostre curve notiamo che esse hanno sempre un effetto di scalettatura. Questo effetto (detto aliasing) può essere eliminato attraverso l’istruzione “smooth ();” da mettere all’inizio dello Sketch. L’effetto sarà di una curva molto più “morbida”. In termini e l a b o r a t iv i q u e s t a g r a d e vo l e f u n z i o n a l i t à e s t e t i c a d i ammorbidimento aumenterà l’impegno del computer. Ovviamente il fenomeno avverrà quando avremo disegnato molte curve e avremo delle animazioni molto veloci. Complichiamo ulteriormente il nostro lavoro introducendo anche un fattore di casualità nella generazione della curva. Questo esercizio è utile perchè viene formulato in modo nuovo uno Sketch contenente solo funzionalità già viste. Lo Sketch mandato in RUN genera una curva di bezier che nel tempo si espande verso destra o verso sinistra seguendo la casualità: int contatore = 10; // la variabile casuale float ai = 1; void setup () { size (320,240); smooth (); } void draw (){ frameRate (10); background (255); // genero un numero casuale ai = random (1); // controllo il numero “estratto” if (ai > 0.5) { contatore = contatore + 2; bezier (100,10, contatore, 10, contatore, 200, 120,200); } else { contatore = contatore - 2; bezier (100,10, contatore, 10, contatore, 200, 120,200); } } 43 processing guida introduttiva alla programmazione visuale La variabile “ai” è legata alla generazione di un numero casuale (con decimali e quindi di tipo float) compreso tra 0 e 1. La probalità che questo numero sia superiore o inferiore a 0.5 è del 50%. Tendenzialmente quindi avremo una curva che si muove c a s u a l m e n t e ve r s o d e s t r a e ve r s o s i n i s t r a ch e p e r ò probabilisticamente tende ad essere stabile (cioè tende a ritornare alla sua posizione di partenza. Modificando l’istruzione “if (ai > 0.5)” ad esempio in “if (ai > 0.4)” altereremo probabilisticamente rapporto e quindi la curva tenderà a modificarsi verso una direzione. I vettori Quando nella programmazione si parla di vettori non ci si riferisce a delle forme grafiche bensì a dei contenitori. Abbiamo già visto nella prima parte il tipo di contenitori più diffusi nella programmazione: le variabili. I vettori sono delle variabili concatenate in sequenza. Il termine inglese per definire i vettori è Array e questi sono alcuni esempi di istruzioni: int[] vettore; float [] vettore2; Nel primo caso abbiamo la dichiarazione di un vettore che conterrà dei valori interi mentre nel secondo caso abbiamo la dichiarazione di un vettore che conterrà dei valori con virgola. Una volta dichiarato il tipo di vettore è necessario inizializzarlo: vettore = new int[50]; vettore2 = new float[150]; Nel primo caso abbiamo inizializzato un vettore che conterrà un massimo di 50 valori diversi mentre nel secondo caso abbiamo inizializzato un vettore che ne conterrà 150. Queste dimensioni sono normali in molti tipi di programmi e questo è proprio il motivo per il quale si usano i vettori anziche le variabili. Nel caso in cui si vogliano immagazzinare 50 informazioni usando le variabili sarebbe necessario inizializzare 50 diverse variabili. I vettori sono quindi il modo più elegante e rapido per organizzare i dati all’interno di un programma. Troviamo i vettori praticamente in tutti i linguaggi di programmazione. 44 processing guida introduttiva alla programmazione visuale Vediamo ora uno Sketch che utilizza i vettori: size (320, 240); smooth (); noFill (); int[] curva = new int[10]; // ciclo per riempire l'array for (int cicli = 0; cicli < 10; cicli = cicli +1) { curva[cicli] = int (random (200)); } // ciclo di lettura dell'array for (int cicli = 0; cicli < curva.length; cicli = cicli +1) { bezier(curva[cicli], 0, curva[cicli], 100, 200, 240, 240, 240); } // secondo ciclo di lettura dell'array for (int cicli = 0; cicli < curva.length; cicli = cicli +1) { ellipse (curva[cicli], 5, 10, 10); } Nella quarta riga osserviamo l’struzione di inizializzazione dell’array: “int[ ] curva = new int[10];” che sarà quindi composto da numeri interi e si chiamerà “curva”. Subito dopo leggiamo un ciclo “for” che ha la funzione di riempire l’array. Il ciclo compie 10 “giri” e mette in ogni casella dell’array un valore casuale compreso tra 0 e 200 (“curva[cicli] = int (random (200))”). Successsivamente abbiamo due altri cicli. Con il primo ciclo vengono disegnate 10 curve i cui parametri sono letti dall’array precedentemente creato ( “bezier(curva[cicli], 0, curva[cicli], 100, 200, 240, 240, 240)”). Da notare che il parametro di chiusura del ciclo “for” viene stabilito attraverso un controllo della lunghezza dell’array (“cicli < curva.length”). Curva.lenght dichiara infatti la lunghezza dell’array che avevamo chiamato “curva”. Il secondo ciclo di lettura dell’array è simile al primo con la differenza che vengono disegnati dei cerchi in corrispondenza del termine superiore delle curve create precedentemente. Questo secondo ciclo è stato creato appositamente per dimostrare l’utilità di un array. Infatti nel secondo ciclo è stato riletto tutto il 45 processing guida introduttiva alla programmazione visuale contenuto memorizzato nell’array e che volendo potrebbe essere ulteriormente letto per funzioni future. L’array quindi ci permette di immagazzinare grandi quantità di dati evitando di dover creare numerose variabili. Inoltre grazie alla loro sequenzialità gli array facilitano l’accesso per la lettura e la scrittura attraverso i cicli. Il seguente Sketch compie le stesse funzioni del precedente in modalità “continuous” generando un effetto di animazione. In questo caso il vettore viene riempito e svuotato automaticamente ad ogni nuovo ciclo automatico di “void draw”. int[] curva = new int[10]; void setup () { size (320, 240); smooth (); noFill (); frameRate (10); } void draw () { // azzero lo schermo background (255); // ciclo per riempire l'array for (int cicli = 0; cicli < 10; cicli = cicli +1) { curva[cicli] = int (random (200)); } // ciclo di lettura dell'array for (int cicli = 0; cicli < curva.length; cicli = cicli +1) { bezier(curva[cicli], 0, curva[cicli], 100, 200, 240, 240, 240); } // secondo ciclo di lettura dell'array for (int cicli = 0; cicli < curva.length; cicli = cicli +1) { ellipse (curva[cicli], 5, 10, 10); } } Notiamo nello Sketch precedente che ad ogni inizio di ciclo viene pulito lo schermo attraverso l’istruzione “background (255);”. Se togliamo questa istruzione vedremo che le curve si sovrapporranno progressivamente andando a formare dopo diversi secondi un intero spicchio nero. Le ultime sezioni bianche che rimangono sono i 46 processing guida introduttiva alla programmazione visuale numeri compresi tra zero e 200 che non sono mai stati generati. Probabilisticamente potrebbero passare anche diversi minuti senza che la funzione random generi un numero. 47 processing guida introduttiva alla programmazione visuale Terza Parte Il testo Processing, come tutti i linguaggi di programmazione, permette di gestire il testo. I due comandi più importanti sono: char (); String (); Qesti due comandi sono in realtà due nuove tipi di variabili. La variabile di tipo “char” può contenere un unico carattere mentre la variabile di tipo “String” può contenere intere parole o frasi. Per assegnare un carattere a una variabile si scrive: char carattere = 'F'; In questo modo la lettera “F” viene memorizzata nella variabile “carattere”. Per assegnare una parole o una frase a una variabile si scrive: String testolungo = “manuale di Processing”; Da notare che “String” inizia con la lettera grande mentre “char” inizia con la lettera piccola. Da ricordare che i comandi di Processing debbono essere scritti tenendo conto anche di maiuscole e minuscole. Inoltre da notare che l’istruzione Char richiede gli apici (‘) per evidenziare il carattere mentre l’istruzione String richiede le virgolette (“). String può contenere anche testi lunghi un inico carattere. Nell’informatica esiste una tabella di corrispondenza tra i caratteri e i numeri. Praticamente ogni lettera dell’alfabeto è stata classificata con un numero in modo sequenziale. Questa tabella di corrispondenza si chiama ASCII e ad esempio la lettera A corrisponde al numero 65. La lettera B corrisponde al numero 66 e così via. Questa tabella di corrispondenza è indispensabile poichè senza di essa il computer (che ragiona per numeri) non potrebbe elaborare i testi. Le variabili di tipo “char”, essendo lunghe un unico carattere, possono essere ricondotte ai numeri e viceversa. Se ad esempio scriviamo: 48 processing guida introduttiva alla programmazione visuale char carattere = ‘F’; int numero = carattere; La variabile “carattere” viene riempita con il valore “F”, poi con la seconda istruzione la variabile “numero” viene riempita con il numero corrispondente alla lettera F (70) secondo il codice ASCII. Questa operazione di conversione bidirezionale dei numeri in lettere e delle lettere in numeri è possibile solamente attraverso l’istruzione “char”. Con l’istruzione “String” essendo essa potenzialmente associata a testi più lunghi di una lettera ciò non è possibile. Attraverso l’espressione “char ()” possiamo richiamare direttabente il codice ASCII del carattere. Ad esempio se scriviamo: char lettera = char(70); Otteniamo che la variabile “lettera” viene associata con la lettera “F” (avente come codice ASCII il numero 70). L’istruzione “String” permette di fare operazioni sul testo. Se ad esempio associamo due variabili a due diverse parole, successivamente possiamo attaccare (sommare) le due parole. String prima = “Manuale di “; String seconda = “Processing”; String somma = prima + seconda; La variabile “somma” avrà come risultato “Manuale di Processing”. Da notare che dopo l’espressione “Manuale di” è stato lasciato appositamente uno spazio in modo che esso risulta presente nella somma delle due variabili. Il modo più velore per vedere il risultato di queste operazioni sule variabili testuali (ma anche per quelle numeriche) è l’uso dell’istruzione “println(somma);” Questa istruzione “println” mostra un valore nella finestra della console. La stessa finestra dell’ambiente di sviluppo dove ci appaiono gli errori. Questa finestra è utile quindi anche quando si sta lavorando ad un programma e si voglieno controllare dei valori intermedi e temporanei di alcune variabili. Caratteri sullo schermo Processing utilizza i font che sono stati precedentemente dichiarati. A differenza dei normali programmi di scrittura, dove troviamo il menù a tendoina con tutti i font disponibili, Processing, essendo un 49 processing guida introduttiva alla programmazione visuale ambiente di sviluppo permette di utilizzare qualunque font che sia stato precedentemente impostato. Infatti Processing utilizza solamente tipi di carattere in formato VLW. Quando all’interno di uno Sketch sviluppato in Processing intendiamo utilizzare un font dobbiamo prima di tutto importare il font stesso attraverso il “Menù Tool” comando “Create Font”. Dando questo comando si apre una finestra nella quale compare la lista dei caratteri installati nel proprio computer. Si seleziona il carattere e la relativa dimensione che si vuole importare e cliccando su OK verrà automaticamente creato un file che poi potrà essere richiamato all’interno dello Sketch. Se ad esempio vogliamo utilizzare il font Courier nella dimensione 12 all’interno del nostro Sketch dopo avere generato il file dovremo richiamare un file di questo nome: “Courier-12.vlw”. Ecco uno Sketch: size (320, 240); background (255); // Inizializzo la variabile PFont carattere; // Carico il font nello Sketch carattere = loadFont("Courier-12.vlw"); // Stabilisco il font che sto per usare textFont(carattere); fill(0); // Scrivo il testo text("Manuale di Processing", 30, 100); Con “PFont” viene inizializzata una variabile che conterrà il nome del font stesso. Con “carattere = loadFont("Courier-12.vlw");” carico nello Sketch il font che intendo utilizzare e ovvimente potrei carirarne anche più di uno. Poi successivamente, quando starò per utilizzare il font, dichiaro il suo uso con “textFont(carattere);” e con “fill (0);” stabilisco che il carattere sarà di colore nero. Con l’istruzione “text” dichiariamo il testo che vogliamo scrivere (potremmo in alernativa mettere il nome di una variabile String) in seguito troviamo le coordinate X,Y dove comparirà il testo. Nel seguente Sketch abbiamo la situazione del precedente con l’unica differenza che attraverso l’istruzione “textSize(18);” il testo avrà una dimensione diversa da quella importata. 50 processing guida introduttiva alla programmazione visuale size (320, 240); background (255); PFont carattere; carattere = loadFont("Courier-12.vlw"); textFont(carattere); textSize(18); fill(0); text("Manuale di Processing", 30, 100); Questa operazione di ingrandimento rappresenta un degradamento della qualità del carattere che risulterà sgranato tanto più sarà stato ingrandito. Infatti atttaverso l’azione di importazione del carattere abbiamo importato delle immagini bitmap (pixel per pixel) del font. Qualitativamente è quindi consigliabile importare i caratteri in tutte le dimensioni che si vorranno utilizzare. Il seguente Sketch è invece un semplice esempio di animazione di un testo. Si tratta all’incirca della somma dello Sketch precedente e dell Sketch utilizzato nelle pagine precedenti per animare un punto facendolo scorrere da sinistra a destra: void setup () { size (320, 240); } int intervallo = 0; void draw (){ background (255); PFont carattere; carattere = loadFont("Courier-12.vlw"); frameRate (10); textFont(carattere); textSize(12); fill(0); text("ciao a tutti", intervallo, 120); intervallo = intervallo + 1; } Lavorare con le Stringhe Processing permette di lavorare con le stringhe attraverso delle apposite istruzioni. Se ad esempio abbiamo la variabile “testolungo” associata ad una string: 51 processing guida introduttiva alla programmazione visuale String testolungo = “manuale di Processing”; Possiamo misurare la lunghezza della stringa attraverso l’istruzione lunghezza = testolungo.length (); Dove lunghezza sarà una variabile di tipo “int” e in questo caso restituirà il numero “21”, ossia la lunghezza del testo “manuale di Processing” inclusi gli spazi tra una parola e l’altra. Mentre con la linea di comando: char iniziale = (testolungo.charAt(0)); Trasferisce il primo carattere (indicato con la posizione 0) della variabile “testolungo” nella variabile iniziale. Pertanto charAt(1) restituirà la “a” di “manuale” mentre il numero 6 ci darà la lettera “e”. Il numero 20 la lettera “g” di “Processing”. Attraverso l’istruzione substring (); possiamo invece prendere delle parti di una scringa ed associarle ad un’altra variabile (oppore stamparle...). string pezzo = testolungo.substring(2,6); Con questa linea di istruzione abbiamo trasferito in una nuova variabile “pezzo” tutte le lettere comprese tra la posizione 2 e la posizione 6 esclusa della variabile “testolungo”. In pratica abbiamo trasferito “nual” nella variabile pezzo. Il seguente Sketch produce una linea la cui lunghezza è inversamente legata alla lunghezza di un testo associato ad una variabile di tipo “String”. size (320, 240); String testolungo = "Manuale Processing" ; // misuro la lunghezza di testolungo int lunghezza = testolungo.length (); // memorizzo il primo carattere // nella variabile “iniziale” char iniziale = (testolungo.charAt(0)); //produco una linea //usando la lunghezza line (lunghezza, lunghezza, 100, 100); 52 di processing guida introduttiva alla programmazione visuale Notiamo che il primo punto della linea è legato alla variabile “lunghezza” che abbiamo utilizzato per misurale la lunghezza del testo “manuale di Processing”. Aggiungesto altre parole al testo “manuale di Processing” e mandando in RUN lo Sketch vediamo che la linea tende progressivamente ad accorciarsi. 53 processing guida introduttiva alla programmazione visuale Quarta Parte Grafica e Movimenti più complessi Processing è un software specializzato per la grafica e le animazioni. Tutte le istruzioni che abbiamo visto fino a questo punto ci hanno permesso di comprendere i principi fondamentali della computergrafica bidimensionale. Le forme che abbiamo disegnato fino ad ora sono relativamente semplici. Abbiamo visto con le curve di Bezier come sia possibile disegnare qualunque tipo di curva utilizzando 4 paramentri. Vediamo ora prima di tutto come animare le forme grafiche. In realtà abbiamo già visto come sia possibile, attraverso l’uso dei contatori e la pulizia dello schermo , animare curve e punti. Processing ci viene incontro in queste funzionalità di animazioni attraverso tre funzioni di animazione: translate (); rotate (); scale (); Attraverso queste tre semplici istruzioni possiamo applicare degli effetti graficamente complessi a tutte le immagini presenti all’interno del nostro Sketch. Sappiamo che scrivendo queste due istruzioni: size (320, 240); line(10, 20, 100, 120); otteniamo una linea che attraverso lo schermo. Attraverso l’istruzione “translate();” abbiamo la possibilità di traslare la linea sulle coordinate X e Y. Infatti se scriviamo: size (320, 240); translate(100, 0); line(10, 20, 100, 120); succede che la linea, a differenza del precedente Sketch, avrà delle cordinate traslate di 100 pixel sull’asse delle X. Le nuove coordinate della linea saranno: (110, 20, 200, 120). I due valori X dei due punti sono stati incrementati di 100 pixel. 54 processing guida introduttiva alla programmazione visuale Ecco il risultato aritmetico dell’istruzione: line (10 + 100, 20 + 0, 100 + 100, 120 + 0); Ovviamente possiamo traslare sia le coordinate X che le Y, ecco un esempio: size (320, 240); translate(100, 25); line(10, 20, 100, 120); La linea disegnata sullo schermo avrà le seguenti coordinate: line (110, 45, 200, 145); L’istruzione traslate è attiva per tutte le forme grafiche che appaino successivamente ad essa. Pertanto se tracciamo due linee dopo l’istruzione traslate otteniamo che tutte e due le linee saranno traslate. size (320, 240); translate(100, 25); line (10, 20, 100, 120); line (20, 40, 200, 100); Mandando in RUN lo Sketch vediamo che sia la prima che la seconda linea risultano traslate. Inoltre le traslazioni si sommano, pertanto se vogliamo ulteriormente traslare una linea dopo una prima traslazione sarà sufficiente inserire la nuova traslazione. size (320, 240); translate(100, 25); line (10, 20, 100, 120); translate(80, 15); line (20, 40, 200, 100); La seconda linea del precedente Sketch è traslata nelle sue coordinate di 180 (100+80) pixel sull’asse “X” e 40 pixel sull’asse “Y” (25+15). Mentre la prima linea risulta traslata solamente dei valori del primo comando traslate. 55 processing guida introduttiva alla programmazione visuale Per ripristinare le condizioni originarie è sufficiente mettere in negativo i valori di traslate. Nel sequente Sketch le coordinate della seconda linea sono “normali”. size (320, 240); translate(100, 25); line (10, 20, 100, 120); translate(-100, -25); line (20, 40, 200, 100); Poichè la seconda istruzione “traslate” sottrae esattamente i valori della prima istruzione, la seconda linea avrà le coordinate: line (20, 40, 200, 100); Proprio come indicato nello Sketch stesso. Rotazione I principi della traslazione valgono anche nel caso della rotazione. Essi quindi sono validi finchè non intervenga un nuovo valore che si somma al precedente. Per capire l’unità di misura dela rotazione dobbiamo capire il concetto di radiante. Normalmente misuriamo i giri in gradi, quindi un giro completo è di 360 gradi mentre un giro di 180 gradi è mezzo giro. Una ruotazione ad angolo retto è una ruotazione di 90 gradi. I radianti sono legati alla costante matematica detta “Pi greco” “PI” o “π”. Il pi greco misura il rapporto tra una qualunque circonferenza di un cerchio e il suo relativo diametro. Questo rapporto è costante e viene sintetizzato in 3.14 anche se i decimali dopo la virgola sono infiniti. La corrispondenza tra i gradi e i radianti è molto semplice: - due radianti (2 * π) sono 360 gradi - un radiante (π) è 180 gradi - mezzo radiante (π/2) è 90 gradi In processing π si scrive PI. Il seguente Sketch mostra la ruotazione di una stessa linea di 90 gradi: size (240, 240); line(0, 0, 100, 0); rotate(PI/2); line(0, 0, 100, 0); 56 processing guida introduttiva alla programmazione visuale Mandando in RUN lo Sketch lediamo una linea in alto orizzontale (la prima linea dello Sketch) e una seconda linea verticale che è la seconda linea dello Sketch. La seconda linea ha subito quindi una rotazione di 90 gradi in senso orario. Le dimensione dello schermo questa volta sono state impostate a (240, 240) al fine di facilitare la comprensione dei gradi. Osserviamo infatti una rotazione di 45 gradi: size (240, 240); line(0, 0, 240, 0); rotate(PI/4); line(0, 0, 240, 0); Vediamo che la seconda linea in questo caso è ruotata di 45 gradi e grazie al fatto che la finestra è quadrata possiamo osservare che essa traccia una pate della diagonale della finestra stessa. Di seguito vediamo tre linee. La prima non subisce alcuna ruotazione mentre la seconda subisce una ruotazione di 45 gradi (PI/4) e la terza ruota di 90 gradi (PI/4 + PI/4 = PI/2). size (240, 240); line(0, 0, 240, 0); rotate(PI/4); line(0, 0, 240, 0); rotate(PI/4); line(0, 0, 240, 0); La funzione di ruotazione funziona quindi come la funzione di traslazione. Per eliminare il suo effetto è sufficiente riproporre il valore in negativo: size (240, 240); line(0, 0, 240, 0); rotate(PI/4); line(0, 0, 240, 0); rotate(- PI/4); strokeWeight (5); line(0, 0, 200, 0); In questo caso la terza linea sembra scomparire mentre in realtà essa è perfettamente sovrapposta alla prima linea. Per questo motivo ho 57 processing guida introduttiva alla programmazione visuale aumentato lo spessore della linea “strokeWeight (5);” inoltre ho leggermente accorciato la lua lunghezza in modo che si possa cogliere anche la prima linea. Infine per comprendere completamente il tipo di ruotazione che avviene mandiamo in RUN il seguente Sketch size (240, 240); ellipse (100, 0, 20, 20); rotate(PI/4); ellipse (100, 0, 20, 20); Vediamo anche in questo caso che il cerchio si sposta. Mentre se il cerchio ruotasse su se stesso non dovremmo vedere alcuno spostamento. In effetti con il comando rotate così come con il comando translate ciò che ruota non è l’oggetto bensì la finestra con tutto ciò che è disegnato al suo interno. Questa ruotazione fa perno sulla coordinata (0,0) dello schermo. Scala Per ingrandire o rimpiccolire un oggetto si utilizza l’istruzione scale ();. Il seguente Sketch mostra due cerchi dove il secondo cerchio è stato raddoppiato nelle sue dimensioni: size (240, 240); ellipse (100, 100, 20, 20); scale (2); ellipse (100, 100, 20, 20); Le coordinate del cerchio cambiano proprio perchè è la finestra che si raddoppia nella sua dimensione con tutti i suoi contenuti. Il seguente Sketch mostra tre cerchi. Il terzo cerchio ha le stesse proporzioni del primo ma ho modificato il riempimento e ridimensionato il raggio. size (240, 240); ellipse (100, 100, 20, 20); scale (2); ellipse (100, 100, 20, 20); scale (0.5); fill (100,20); 58 processing guida introduttiva alla programmazione visuale ellipse (100, 100, 10, 10); Vediamo che attraverso l’istruzione “scale (0.5);” è stato diviso per due il precedente rapporto di ingrandimento “scale(2)”. In questo modo il rapporto finale è 0.5*2 cioè 1. Quest’ultimo Sketch ci mostra tre cerchio scalati ogni volta per due. Il terzo cerchio sarà quindi 4 volte (2*2) più grande del primo. size (240, 240); ellipse (50, 50, 20, 20); scale (2); ellipse (50, 50, 20, 20); scale (2); ellipse (50, 50, 20, 20); Da notare che anche la distanza tra il secondo e il terzo cerchio è doppia rispetto alla distanza tra il primo e il secondo cerchio. Infine notiamo che le tre funzionalità di modifica delle forme grafiche possono ovviamente essere utilizzate in modo combinato. size (240, 240); ellipse (50, 50, 20, 20); scale (2); ellipse (50, 50, 20, 20); translate (20, 0); ellipse (50, 50, 20, 20); Nel precedente Sketch i cerchio risulterà traslato di 20 pixel sull’asse delle X e ingrandito di due volte. Il secondo cerchio ha invece subito solamente l’ingrandimento ma non la traslazione. 59 processing guida introduttiva alla programmazione visuale Quinta parte Arduino Arduino è una piattaforma open source per lo sviluppo e la progettazione di oggetti interattivi. Lo scopo principale di questa scheda è quella di dare la possibilità a sviluppatori e artisti di progettare prototipi letteralmente “giocando” con l’elettronica e sperimentando con sensori, attuatori e controller elettronici. Questa particolare ricerca nell’interazione tra il calcolatore e il mondo fisico è nota come “Physical Computing” ed era fino a pochi anni fa una disciplina conosciuta e utilizzata solo in campo ingegneristico, che richiedeva una conoscenza avanzata dell’elettrotecnica e del software. Questo livello di complessità ha tenuto per anni lontani gli ideatori, gli obbisti e gli artisti dalle infinite possibilità che l’elettronica può aprire nel mondo del design e dell’arte in generale. Lo scopo degli sviluppatori di Arduino è quello di fornire uno strumento semplice che permetta a chiunque di iniziare a costruire progetti interattivi in modo immediato ed economico. Grazie a questa semplicità di utilizzo e alla crescente diffusione, oggi Arduino oltre a risolvere numerosi problemi nello sviluppo e nella realizzazione di oggetti interattivi, è alla base di una nuova filosofia che cresce e si alimenta dal mondo dell’open source e dalla condivisione di conoscenze tipica della rete. Nel mondo anglosassone questo “modo di procedere” nello sviluppo è noto come “Tinkering”, una forma di deriva creativa guidata dall’immaginazione e dalla curiosità. Mentre il percorso di sviluppo ingegneristico classico presuppone una progettazione che procede in modo razionale dal punto A al punto B, il Tinkering presuppone il perdersi in questo percorso e scoprire un nuovo punto C. Tinkering significa in pratica trovare risultati curiosi provando e sbagliando procedimenti ignoti. 60 processing guida introduttiva alla programmazione visuale Costruire senza le istruzioni, giocando con i fallimenti, non preoccupandosi del modo giusto o sbagliato di fare le cose. Uno dei modi migliori di utilizzare questo processo creativo è riutilizzare tecnologie esistenti, smontare vecchi apparecchi elettronici e usarne parti interessanti sfruttandole per utilizzi lontani da quelli per cui erano state progettate. Questo approccio spontaneo e casuale è alla base di alcune nuove forme d’arte come il Circuit Bending: un genere musicale suonato con vecchi strumenti giocattolo elettronici, smontati e modificati per creare suoni completamente diversi da quelli pensati dal costruttore originario. Arduino insomma rappresenta oggi una nuova ottica con cui guardare la tencologia che ci circonda, sviluppare nuove funzioni per strumenti vecchi ed esplorare nuove possibilità senza il bisogno di perdersi in anni di studi ma semplicemente giocando con ciò che già abbiamo a portata di mano sfruttando l’istinto di ogni bambino... smontare i giocattoli per vedere cosa c’è dentro. L’Hardware: Arduino è una scheda composta principalmente da un processore e una serie di ingressi e uscite sia digitali che analogici, alimentabile sia via USB che tramite un alimentatore esterno. 61 processing guida introduttiva alla programmazione visuale Il Software: Il processore di Arduino è programmabile attraverso un ambiente di sviluppo(IDE) basato su Processing. Tutto il software necessario all’utilizzo di Arduino oltre a svariati esempi di codice è scaricabile gratuitamente dal sito www.arduino.cc Una volta scritto il programma nell’editor principale (proprio come un programma in Processing) va controllata la sua correttezza, premendo verify, se il codice è scritto correttamente apparirà il messaggio Done compiling, a questo punto é possibile caricare il programma nel processore di Arduino. Premendo il bottone reset su Arduino si hanno 6 secondi per premere Upload to I/O board nella parte alte dell’IDE. Questo procedimento invia il programma alla scheda, lo salva nella sua memoria e lo esegue automaticamente. 62 processing guida introduttiva alla programmazione visuale La BreadBoard Prima che il vostro circuito funzioni perfettamente ci sarà bisogno di vari tentativi e saldare diversi tentativi su una piastra è un lavoro lungo e dispendioso. L’equivalente elettronico dello schizzo iniziale di un progetto fatto su un foglio di carta è la BreadBoard. Si tratta in pratica di una piastra di plastica piena di fori ognuno dei quali contiene un contatto. Quando si mette un componente in uno di questi fori si crea un contatto elettrico con tutti gli altri fori della stessa colonna verticale. Le due coppie di linee di fori in alto e in basso della Breadboard (solitamente colorate di rosso e blu e con il segno+ e -) sono invece collegate in orizzontale e sono usate per portare l’alimentazione in tutta la piastra. 63 processing guida introduttiva alla programmazione visuale L’Interazione L’utilizzo piu’ frequente di Arduino è lo sviluppo di oggetti interattivi. Il funzionamento base che accomuna questo dipo di dispositivi segue uno schema molto semplice: Le informazioni che il dispositivo legge dal mondo esterno attraverso dei sensori vengono elaborate dal programma all’interno del processore, questo deciderà il comportamento del dispositivo che interagirà di consegueza con il mondo esterno attraverso degli attuatori. Sensori e attuatori sono componenti elettronici, i primi “leggono” delle informazioni dal mondo fisico e le trasformano in segnali interpretabili dal processore, i secondi convertono i segnali del processore in azioni fisiche. Per semplificare queste definizioni con un esempio, nel corpo umano, gli occhi sono dei sensori che convertono la luce in immagini, il cervello (nel nostro caso il processore) elabora le immagini e manda segnali ai muscoli (attuatori). 64 processing guida introduttiva alla programmazione visuale Un primo esempio pratico Vediamo con un esempio pratico come lo schema precedente di interazione possa trovare un applicazione con Arduino. Costruiamo un semplice dispositivo che accende un LED (lightemitting diode) quando viene premuto un bottone. In questo caso il bottone rappresenta il sensore e il LED l’attuatore. Iniziamo costruendo il semplice circuito illustrato nella seguente figura. 65 processing guida introduttiva alla programmazione visuale N.B. il LED va inserito con il contatto piu’ corto nel pin GND e con quello piu’ lungo nel pin 13. Scriviamo poi nell’IDE il seguente programma: // primo esempio di interazione con Arduino const int buttonPin = 7; // numero del pin del bottone const int ledPin = 13; // numero del pin del LED int buttonState = 0; // variabile che legge il bottone void setup() { // inizializza il pin del LED come output pinMode(ledPin, OUTPUT); // inizializza il pin del bottone come input pinMode(buttonPin, INPUT); } void loop(){ // legge lo stato del bottone buttonState = digitalRead(buttonPin); // controlla se il bottone e’ premuto // se e’ premuto e’ LOW if (buttonState == LOW) { // accende il LED digitalWrite(ledPin, HIGH); 66 processing guida introduttiva alla programmazione visuale } else { // spegne il LED digitalWrite(ledPin, LOW); } } Carichiamo il codice nella scheda e vediamo che il LED si illumina quando il pulsante viene premuto. Questo avviene perchè la funzione digitalRead(); legge se un voltaggio passa nel pin precedentemente dichiarato, se il voltaggio è inferiore a 2.5V (bottone premuto) allora la funzione ritornerà un valore LOW e di conseguenza accenderà il LED con la funzione: digitalWrite(ledPin, HIGH); mentre se passerà un voltaggio superiore allora ritornerà un valore HIGH (che corrisponde al bottone non premuto). Proprio come in un programma in Processing dobbiamo inizialmente dichiarare variabili e costanti, anche nel programmare Arduino è necessario dichiarare come i pin della scheda verranno utilizzati. all’interno della funzione void setup(); dobbiamo configurare se il comportamento di un determinato pin sarà di INPUT o di OUTPUT. Nel nostro precedente esempio con il codice pinMode(ledPin, OUTPUT); abbiamo dichiarato che il pin 13 verrà utilizzato come Output mentre con 67 processing guida introduttiva alla programmazione visuale pinMode(buttonPin, INPUT); dichiariamo che il pin 7 si comporterà come INPUT. La funzione digitalRead(pin); legge il valore HIGH o quello LOW da uno specifico pin digitale mentre quella analogRead(pin); legge un valore intero da 0 a 1023 da uno dei pin da 0 a 5. La funzione digitalWrite(pin, valore); manda in output un valore HIGH o uno LOW ad uno specifico pin. Mentre la funzione analogWrite(pin, valore); manda un valore analogico da 0 a 255 ad uno dei pin 9, 10 o 11 (nei modelli di Arduino più nuovi, quelli con il processore ATmega 168 si possono utilizzare i pin 3, 5, 6, 9, 10 e 11). In questo caso ad un valore di 0 corrisponde 0 volts mentre ad un valore di 255 corrisponde un voltaggio di 5V. 68 processing guida introduttiva alla programmazione visuale Questo manuale è scritto sotto licenza Creative Commons e gli autori accettano volentieri proposte di nuove parti da aggiungere ad esso. Chi volesse proporsi può scrivere a: Alberto Cecchi: [email protected] Valerio Belloni: [email protected] processing guida introduttiva alla programmazione visuale ver. 0.9 oct. 2009 69